package com.cfdeployer.controller;

import com.cfdeployer.model.CfDeployRequest;
import com.cfdeployer.model.CfDeployResponse;
import com.cfdeployer.model.ChunkUploadRequest;
import com.cfdeployer.model.ChunkUploadResponse;
import com.cfdeployer.model.DeploymentStatus;
import com.cfdeployer.service.AsyncDeploymentService;
import com.cfdeployer.service.CfCliService;
import com.cfdeployer.service.ChunkedUploadService;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/cf")
@RequiredArgsConstructor
public class CfDeployController {

    private static final Logger log = LoggerFactory.getLogger(CfDeployController.class);

    private final CfCliService cfCliService;
    private final ChunkedUploadService chunkedUploadService;
    private final AsyncDeploymentService asyncDeploymentService;
    private final ObjectMapper objectMapper;

    @PostMapping(value = "/deploy", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<CfDeployResponse> deploy(
            @RequestPart("request") String requestJson,
            @RequestPart("jarFile") MultipartFile jarFile,
            @RequestPart("manifest") MultipartFile manifest) {

        try {
            log.info("Received deployment request");

            CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
            log.info("Deploying application: {} to org: {} space: {}",
                    request.getAppName(), request.getOrganization(), request.getSpace());

            validateFiles(jarFile, manifest);

            CfDeployResponse response = cfCliService.deployApplication(request, jarFile, manifest);

            if (Boolean.TRUE.equals(response.getSuccess())) {
                return ResponseEntity.ok(response);
            } else {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
            }

        } catch (Exception e) {
            log.error("Error processing deployment request", e);
            CfDeployResponse errorResponse = CfDeployResponse.failure(
                    "Failed to process deployment request: " + e.getMessage(),
                    e.toString()
            );
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
        }
    }

    @PostMapping("/apps")
    public ResponseEntity<String> listApps(@RequestBody String requestJson) {
        try {
            log.info("Received request to list apps");
            CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
            String output = cfCliService.listApps(request);
            return ResponseEntity.ok(output);
        } catch (Exception e) {
            log.error("Error listing apps", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Failed to list apps: " + e.getMessage());
        }
    }

    @PostMapping("/routes")
    public ResponseEntity<String> listRoutes(@RequestBody String requestJson) {
        try {
            log.info("Received request to list routes");
            CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
            String output = cfCliService.listRoutes(request);
            return ResponseEntity.ok(output);
        } catch (Exception e) {
            log.error("Error listing routes", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Failed to list routes: " + e.getMessage());
        }
    }

    @PostMapping("/app/{appName}")
    public ResponseEntity<String> getAppDetails(
            @PathVariable String appName,
            @RequestBody String requestJson) {
        try {
            log.info("Received request to get details for app: {}", appName);
            CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
            String output = cfCliService.getAppDetails(request, appName);
            return ResponseEntity.ok(output);
        } catch (Exception e) {
            log.error("Error getting app details for: {}", appName, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Failed to get app details: " + e.getMessage());
        }
    }

    @PostMapping("/logs/{appName}")
    public ResponseEntity<String> getAppLogs(
            @PathVariable String appName,
            @RequestParam(defaultValue = "true") boolean recent,
            @RequestBody String requestJson) {
        try {
            log.info("Received request to get {} logs for app: {}", recent ? "recent" : "tail", appName);
            CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
            String output = cfCliService.getAppLogs(request, appName, recent);
            return ResponseEntity.ok(output);
        } catch (Exception e) {
            log.error("Error getting logs for: {}", appName, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Failed to get app logs: " + e.getMessage());
        }
    }

    // Chunked upload endpoints
    @PostMapping("/upload/init")
    public ResponseEntity<ChunkUploadResponse> initUpload(@RequestBody String requestJson) {
        try {
            log.info("Initializing chunked upload session");

            // Validate the request JSON
            CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
            log.info("Creating upload session for app: {}", request.getAppName());

            String sessionId = chunkedUploadService.createUploadSession(requestJson);

            return ResponseEntity.ok(ChunkUploadResponse.builder()
                    .success(true)
                    .uploadSessionId(sessionId)
                    .message("Upload session created successfully")
                    .build());
        } catch (Exception e) {
            log.error("Error initializing upload session", e);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(ChunkUploadResponse.failure("Failed to initialize upload: " + e.getMessage()));
        }
    }

    @PostMapping("/upload/chunk")
    public ResponseEntity<ChunkUploadResponse> uploadChunk(
            @RequestParam("uploadSessionId") String uploadSessionId,
            @RequestParam("fileType") String fileType,
            @RequestParam("chunkIndex") Integer chunkIndex,
            @RequestParam("totalChunks") Integer totalChunks,
            @RequestParam(value = "fileName", required = false) String fileName,
            @RequestPart("chunk") MultipartFile chunk) {
        try {
            log.debug("Receiving chunk {}/{} for session: {}, fileType: {}",
                    chunkIndex + 1, totalChunks, uploadSessionId, fileType);

            // Validate file type
            if (!fileType.equals("jarFile") && !fileType.equals("manifest")) {
                throw new IllegalArgumentException("Invalid file type. Must be 'jarFile' or 'manifest'");
            }

            chunkedUploadService.uploadChunk(uploadSessionId, fileType, fileName,
                    chunkIndex, totalChunks, chunk);

            var session = chunkedUploadService.getSession(uploadSessionId);
            var fileState = session.getFileStates().get(fileType);

            return ResponseEntity.ok(ChunkUploadResponse.success(
                    uploadSessionId, fileType, chunkIndex, totalChunks,
                    fileState.getReceivedChunkCount()));
        } catch (Exception e) {
            log.error("Error uploading chunk", e);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(ChunkUploadResponse.failure("Failed to upload chunk: " + e.getMessage()));
        }
    }

    @PostMapping("/upload/finalize")
    public ResponseEntity<DeploymentStatus> finalizeUpload(
            @RequestParam("uploadSessionId") String uploadSessionId,
            @RequestParam(value = "async", defaultValue = "true") boolean async) {
        try {
            log.info("Finalizing upload for session: {} (async={})", uploadSessionId, async);

            if (!chunkedUploadService.isSessionReady(uploadSessionId)) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body(DeploymentStatus.builder()
                                .uploadSessionId(uploadSessionId)
                                .status(DeploymentStatus.Status.FAILED)
                                .message("Upload incomplete. Not all file chunks received.")
                                .build());
            }

            var session = chunkedUploadService.getSession(uploadSessionId);
            if (session == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(DeploymentStatus.builder()
                                .uploadSessionId(uploadSessionId)
                                .status(DeploymentStatus.Status.FAILED)
                                .message("Upload session not found or expired")
                                .build());
            }

            CfDeployRequest request = objectMapper.readValue(session.getRequestJson(), CfDeployRequest.class);
            log.info("Starting deployment for app: {} from session: {}",
                    request.getAppName(), uploadSessionId);

            // Get file paths from session
            var jarState = session.getFileStates().get("jarFile");
            var manifestState = session.getFileStates().get("manifest");

            if (async) {
                // Start async deployment - returns immediately
                asyncDeploymentService.deployAsync(uploadSessionId, request,
                        jarState.getTargetPath(), manifestState.getTargetPath());

                return ResponseEntity.accepted().body(DeploymentStatus.builder()
                        .uploadSessionId(uploadSessionId)
                        .status(DeploymentStatus.Status.IN_PROGRESS)
                        .message("Deployment started. Use /deployment/status endpoint to check progress.")
                        .progress(0)
                        .build());
            } else {
                // Synchronous deployment - waits for completion (may timeout!)
                CfDeployResponse response = cfCliService.deployApplicationFromPaths(
                        request,
                        jarState.getTargetPath(),
                        manifestState.getTargetPath());

                // Clean up session after deployment
                chunkedUploadService.deleteSession(uploadSessionId);

                if (Boolean.TRUE.equals(response.getSuccess())) {
                    return ResponseEntity.ok(DeploymentStatus.builder()
                            .uploadSessionId(uploadSessionId)
                            .status(DeploymentStatus.Status.COMPLETED)
                            .message(response.getMessage())
                            .output(response.getOutput())
                            .progress(100)
                            .build());
                } else {
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                            .body(DeploymentStatus.builder()
                                    .uploadSessionId(uploadSessionId)
                                    .status(DeploymentStatus.Status.FAILED)
                                    .message(response.getMessage())
                                    .error(response.getError())
                                    .build());
                }
            }
        } catch (Exception e) {
            log.error("Error finalizing upload", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DeploymentStatus.builder()
                            .uploadSessionId(uploadSessionId)
                            .status(DeploymentStatus.Status.FAILED)
                            .message("Failed to finalize deployment: " + e.getMessage())
                            .error(e.toString())
                            .build());
        }
    }

    @GetMapping("/deployment/status/{uploadSessionId}")
    public ResponseEntity<DeploymentStatus> getDeploymentStatus(@PathVariable String uploadSessionId) {
        try {
            DeploymentStatus status = asyncDeploymentService.getDeploymentStatus(uploadSessionId);

            // Clean up session and deployment status if completed or failed
            if (status.getStatus() == DeploymentStatus.Status.COMPLETED ||
                status.getStatus() == DeploymentStatus.Status.FAILED) {
                chunkedUploadService.deleteSession(uploadSessionId);
            }

            return ResponseEntity.ok(status);
        } catch (Exception e) {
            log.error("Error getting deployment status", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DeploymentStatus.builder()
                            .uploadSessionId(uploadSessionId)
                            .status(DeploymentStatus.Status.FAILED)
                            .message("Failed to get deployment status: " + e.getMessage())
                            .build());
        }
    }

    @GetMapping("/upload/status/{uploadSessionId}")
    public ResponseEntity<?> getUploadStatus(@PathVariable String uploadSessionId) {
        try {
            var session = chunkedUploadService.getSession(uploadSessionId);
            if (session == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body("Upload session not found or expired");
            }

            return ResponseEntity.ok(session.getFileStates());
        } catch (Exception e) {
            log.error("Error getting upload status", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Failed to get upload status: " + e.getMessage());
        }
    }

    private void validateFiles(MultipartFile jarFile, MultipartFile manifest) {
        if (jarFile.isEmpty()) {
            throw new IllegalArgumentException("JAR file is empty");
        }

        if (manifest.isEmpty()) {
            throw new IllegalArgumentException("Manifest file is empty");
        }

        String jarFileName = jarFile.getOriginalFilename();
        if (jarFileName == null || !jarFileName.toLowerCase().endsWith(".jar")) {
            throw new IllegalArgumentException("Invalid JAR file. File must have .jar extension");
        }

        String manifestFileName = manifest.getOriginalFilename();
        if (manifestFileName == null ||
            (!manifestFileName.toLowerCase().endsWith(".yml") && !manifestFileName.toLowerCase().endsWith(".yaml"))) {
            throw new IllegalArgumentException("Invalid manifest file. File must have .yml or .yaml extension");
        }

        log.debug("File validation successful - JAR: {}, Manifest: {}", jarFileName, manifestFileName);
    }
}
