package com.cameleer.server.app.controller;

import com.cameleer.server.app.runtime.DeploymentExecutor;
import com.cameleer.server.app.web.EnvPath;
import com.cameleer.server.core.admin.AuditCategory;
import com.cameleer.server.core.admin.AuditResult;
import com.cameleer.server.core.admin.AuditService;
import com.cameleer.server.core.runtime.App;
import com.cameleer.server.core.runtime.AppService;
import com.cameleer.server.core.runtime.AppVersion;
import com.cameleer.server.core.runtime.AppVersionRepository;
import com.cameleer.server.core.runtime.Deployment;
import com.cameleer.server.core.runtime.DeploymentService;
import com.cameleer.server.core.runtime.Environment;
import com.cameleer.server.core.runtime.EnvironmentService;
import com.cameleer.server.core.runtime.RuntimeOrchestrator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Deployment management. Env + app come from the URL. Promote is inherently
 * cross-env, so the target environment stays explicit in the request body
 * (as a slug).
 */
@RestController
@RequestMapping("/api/v1/environments/{envSlug}/apps/{appSlug}/deployments")
@Tag(name = "Deployment Management", description = "Deploy, stop, promote, and view logs")
@PreAuthorize("hasAnyRole('OPERATOR', 'ADMIN')")
public class DeploymentController {

    private final DeploymentService deploymentService;
    private final DeploymentExecutor deploymentExecutor;
    private final RuntimeOrchestrator orchestrator;
    private final AppService appService;
    private final EnvironmentService environmentService;
    private final AuditService auditService;
    private final AppVersionRepository appVersionRepository;

    public DeploymentController(DeploymentService deploymentService,
                                 DeploymentExecutor deploymentExecutor,
                                 RuntimeOrchestrator orchestrator,
                                 AppService appService,
                                 EnvironmentService environmentService,
                                 AuditService auditService,
                                 AppVersionRepository appVersionRepository) {
        this.deploymentService = deploymentService;
        this.deploymentExecutor = deploymentExecutor;
        this.orchestrator = orchestrator;
        this.appService = appService;
        this.environmentService = environmentService;
        this.auditService = auditService;
        this.appVersionRepository = appVersionRepository;
    }

    @GetMapping
    @Operation(summary = "List deployments for this app in this environment")
    @ApiResponse(responseCode = "200", description = "Deployment list returned")
    public ResponseEntity<List<Deployment>> listDeployments(@EnvPath Environment env, @PathVariable String appSlug) {
        try {
            App app = appService.getByEnvironmentAndSlug(env.id(), appSlug);
            return ResponseEntity.ok(deploymentService.listByApp(app.id()));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.notFound().build();
        }
    }

    @GetMapping("/{deploymentId}")
    @Operation(summary = "Get deployment by ID")
    @ApiResponse(responseCode = "200", description = "Deployment found")
    @ApiResponse(responseCode = "404", description = "Deployment not found")
    public ResponseEntity<Deployment> getDeployment(@EnvPath Environment env,
                                                     @PathVariable String appSlug,
                                                     @PathVariable UUID deploymentId) {
        try {
            return ResponseEntity.ok(deploymentService.getById(deploymentId));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping
    @Operation(summary = "Create and start a new deployment for this app in this environment")
    @ApiResponse(responseCode = "202", description = "Deployment accepted and starting")
    public ResponseEntity<Deployment> deploy(@EnvPath Environment env,
                                              @PathVariable String appSlug,
                                              @RequestBody DeployRequest request,
                                              HttpServletRequest httpRequest) {
        try {
            App app = appService.getByEnvironmentAndSlug(env.id(), appSlug);
            AppVersion appVersion = appVersionRepository.findById(request.appVersionId())
                    .orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + request.appVersionId()));
            Deployment deployment = deploymentService.createDeployment(app.id(), request.appVersionId(), env.id(), currentUserId());
            deploymentExecutor.executeAsync(deployment);
            auditService.log("deploy_app", AuditCategory.DEPLOYMENT, deployment.id().toString(),
                    Map.of("appSlug", appSlug, "envSlug", env.slug(),
                            "appVersionId", request.appVersionId().toString(),
                            "jarFilename", appVersion.jarFilename() != null ? appVersion.jarFilename() : "",
                            "version", appVersion.version()),
                    AuditResult.SUCCESS, httpRequest);
            return ResponseEntity.accepted().body(deployment);
        } catch (IllegalArgumentException e) {
            auditService.log("deploy_app", AuditCategory.DEPLOYMENT, null,
                    Map.of("appSlug", appSlug, "envSlug", env.slug(), "error", e.getMessage()),
                    AuditResult.FAILURE, httpRequest);
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping("/{deploymentId}/stop")
    @Operation(summary = "Stop a running deployment")
    @ApiResponse(responseCode = "200", description = "Deployment stopped")
    @ApiResponse(responseCode = "404", description = "Deployment not found")
    public ResponseEntity<Deployment> stop(@EnvPath Environment env,
                                            @PathVariable String appSlug,
                                            @PathVariable UUID deploymentId,
                                            HttpServletRequest httpRequest) {
        try {
            Deployment deployment = deploymentService.getById(deploymentId);
            deploymentExecutor.stopDeployment(deployment);
            auditService.log("stop_deployment", AuditCategory.DEPLOYMENT, deploymentId.toString(),
                    Map.of("appSlug", appSlug, "envSlug", env.slug()),
                    AuditResult.SUCCESS, httpRequest);
            return ResponseEntity.ok(deploymentService.getById(deploymentId));
        } catch (IllegalArgumentException e) {
            auditService.log("stop_deployment", AuditCategory.DEPLOYMENT, deploymentId.toString(),
                    Map.of("appSlug", appSlug, "envSlug", env.slug(), "error", e.getMessage()),
                    AuditResult.FAILURE, httpRequest);
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping("/{deploymentId}/promote")
    @Operation(summary = "Promote this deployment to a different environment",
            description = "Target environment is specified by slug in the request body. "
                    + "The same app slug must exist in the target environment (or be created separately first).")
    @ApiResponse(responseCode = "202", description = "Promotion accepted and starting")
    @ApiResponse(responseCode = "404", description = "Deployment or target environment not found")
    public ResponseEntity<?> promote(@EnvPath Environment env,
                                      @PathVariable String appSlug,
                                      @PathVariable UUID deploymentId,
                                      @RequestBody PromoteRequest request,
                                      HttpServletRequest httpRequest) {
        try {
            Deployment source = deploymentService.getById(deploymentId);
            Environment targetEnv = environmentService.getBySlug(request.targetEnvironment());
            // Target must also have the app with the same slug
            App targetApp = appService.getByEnvironmentAndSlug(targetEnv.id(), appSlug);
            Deployment promoted = deploymentService.promote(targetApp.id(), source.appVersionId(), targetEnv.id(), currentUserId());
            deploymentExecutor.executeAsync(promoted);
            auditService.log("promote_deployment", AuditCategory.DEPLOYMENT, promoted.id().toString(),
                    Map.of("sourceEnv", env.slug(), "targetEnv", request.targetEnvironment(),
                            "appSlug", appSlug, "appVersionId", source.appVersionId().toString()),
                    AuditResult.SUCCESS, httpRequest);
            return ResponseEntity.accepted().body(promoted);
        } catch (IllegalArgumentException e) {
            auditService.log("promote_deployment", AuditCategory.DEPLOYMENT, deploymentId.toString(),
                    Map.of("sourceEnv", env.slug(), "targetEnv", request.targetEnvironment(),
                            "appSlug", appSlug, "error", e.getMessage()),
                    AuditResult.FAILURE, httpRequest);
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(Map.of("error", e.getMessage()));
        }
    }

    @GetMapping("/{deploymentId}/logs")
    @Operation(summary = "Get container logs for this deployment")
    @ApiResponse(responseCode = "200", description = "Logs returned")
    @ApiResponse(responseCode = "404", description = "Deployment not found or no container")
    public ResponseEntity<List<String>> getLogs(@EnvPath Environment env,
                                                 @PathVariable String appSlug,
                                                 @PathVariable UUID deploymentId) {
        try {
            Deployment deployment = deploymentService.getById(deploymentId);
            if (deployment.containerId() == null) {
                return ResponseEntity.notFound().build();
            }
            List<String> logs = orchestrator.getLogs(deployment.containerId(), 200).collect(Collectors.toList());
            return ResponseEntity.ok(logs);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.notFound().build();
        }
    }

    private String currentUserId() {
        var auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || auth.getName() == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No authentication");
        }
        String name = auth.getName();
        return name.startsWith("user:") ? name.substring(5) : name;
    }

    public record DeployRequest(UUID appVersionId) {}
    public record PromoteRequest(String targetEnvironment) {}
}
