package com.cameleer.server.app.controller;

import com.cameleer.server.app.AbstractPostgresIT;
import com.cameleer.server.app.TestSecurityHelper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

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

import static org.assertj.core.api.Assertions.assertThat;

class DeploymentControllerAuditIT extends AbstractPostgresIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestSecurityHelper securityHelper;

    private String aliceJwt;
    private String adminJwt;
    private String appSlug;
    private String versionId;

    @BeforeEach
    void setUp() throws Exception {
        // Mint JWT for alice (OPERATOR) — subject must start with "user:" for JwtAuthenticationFilter
        aliceJwt = securityHelper.createToken("user:alice", "user", List.of("OPERATOR"));
        adminJwt = securityHelper.adminToken();

        // Lift default-tier caps so the promote-target env + apps can be created via the API.
        securityHelper.installSyntheticUnsignedLicense(Map.of(
                "max_environments", 100,
                "max_apps", 100));

        // Clean up deployment-related tables and test-created environments
        jdbcTemplate.update("DELETE FROM deployments");
        jdbcTemplate.update("DELETE FROM app_versions");
        jdbcTemplate.update("DELETE FROM apps");
        jdbcTemplate.update("DELETE FROM environments WHERE slug LIKE 'promote-target-%'");
        jdbcTemplate.update("DELETE FROM audit_log");

        // Ensure alice exists in the users table (required for deployments.created_by FK)
        jdbcTemplate.update(
                "INSERT INTO users (user_id, provider, display_name) VALUES ('alice', 'local', 'Alice Test') ON CONFLICT (user_id) DO NOTHING");

        // Create app in the seeded "default" environment
        appSlug = "audit-test-" + UUID.randomUUID().toString().substring(0, 8);
        String appJson = String.format("""
                {"slug": "%s", "displayName": "Audit Test App"}
                """, appSlug);
        ResponseEntity<String> appResponse = restTemplate.exchange(
                "/api/v1/environments/default/apps", HttpMethod.POST,
                new HttpEntity<>(appJson, authHeaders(aliceJwt)),
                String.class);
        assertThat(appResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        // Upload a JAR version
        byte[] jarContent = "fake-jar-for-audit-test".getBytes();
        ByteArrayResource resource = new ByteArrayResource(jarContent) {
            @Override
            public String getFilename() {
                return "audit-test.jar";
            }
        };
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", resource);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + aliceJwt);
        headers.set("X-Cameleer-Protocol-Version", "1");
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        ResponseEntity<String> versionResponse = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/versions", HttpMethod.POST,
                new HttpEntity<>(body, headers),
                String.class);
        assertThat(versionResponse.getStatusCode().is2xxSuccessful()).isTrue();
        versionId = objectMapper.readTree(versionResponse.getBody()).path("id").asText();
    }

    @org.junit.jupiter.api.AfterEach
    void tearDown() {
        securityHelper.clearTestLicense();
    }

    @Test
    void deploy_writes_audit_row_with_DEPLOYMENT_category_and_alice_actor() throws Exception {
        String json = String.format("""
                {"appVersionId": "%s"}
                """, versionId);

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/deployments", HttpMethod.POST,
                new HttpEntity<>(json, authHeaders(aliceJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);

        Map<String, Object> row = queryAuditRow("deploy_app");
        assertThat(row).isNotNull();
        assertThat(row.get("username")).isEqualTo("alice");
        assertThat(row.get("action")).isEqualTo("deploy_app");
        assertThat(row.get("category")).isEqualTo("DEPLOYMENT");
        assertThat(row.get("result")).isEqualTo("SUCCESS");
        assertThat(row.get("target")).isNotNull();
        assertThat(row.get("target").toString()).isNotBlank();
    }

    @Test
    void stop_writes_audit_row() throws Exception {
        // First deploy
        String deployJson = String.format("""
                {"appVersionId": "%s"}
                """, versionId);
        ResponseEntity<String> deployResponse = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/deployments", HttpMethod.POST,
                new HttpEntity<>(deployJson, authHeaders(aliceJwt)),
                String.class);
        assertThat(deployResponse.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
        String deploymentId = objectMapper.readTree(deployResponse.getBody()).path("id").asText();

        // Clear audit log to isolate stop audit row
        jdbcTemplate.update("DELETE FROM audit_log");

        // Stop the deployment
        ResponseEntity<String> stopResponse = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/deployments/" + deploymentId + "/stop",
                HttpMethod.POST,
                new HttpEntity<>(authHeadersNoBody(aliceJwt)),
                String.class);
        assertThat(stopResponse.getStatusCode()).isEqualTo(HttpStatus.OK);

        Map<String, Object> row = queryAuditRow("stop_deployment");
        assertThat(row).isNotNull();
        assertThat(row.get("username")).isEqualTo("alice");
        assertThat(row.get("action")).isEqualTo("stop_deployment");
        assertThat(row.get("category")).isEqualTo("DEPLOYMENT");
        assertThat(row.get("result")).isEqualTo("SUCCESS");
        assertThat(row.get("target").toString()).isEqualTo(deploymentId);
    }

    @Test
    void promote_writes_audit_row() throws Exception {
        // Create a second environment for promotion target
        String targetEnvSlug = "promote-target-" + UUID.randomUUID().toString().substring(0, 8);
        String envJson = String.format("""
                {"slug": "%s", "displayName": "Promote Target Env"}
                """, targetEnvSlug);
        ResponseEntity<String> envResponse = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(envJson, authHeaders(adminJwt)),
                String.class);
        assertThat(envResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        // Create the same app slug in the target environment
        String appJson = String.format("""
                {"slug": "%s", "displayName": "Audit Test App (target)"}
                """, appSlug);
        ResponseEntity<String> targetAppResponse = restTemplate.exchange(
                "/api/v1/environments/" + targetEnvSlug + "/apps", HttpMethod.POST,
                new HttpEntity<>(appJson, authHeaders(aliceJwt)),
                String.class);
        assertThat(targetAppResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        // Deploy in source (default) env
        String deployJson = String.format("""
                {"appVersionId": "%s"}
                """, versionId);
        ResponseEntity<String> deployResponse = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/deployments", HttpMethod.POST,
                new HttpEntity<>(deployJson, authHeaders(aliceJwt)),
                String.class);
        assertThat(deployResponse.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
        String deploymentId = objectMapper.readTree(deployResponse.getBody()).path("id").asText();

        // Clear audit log to isolate promote audit row
        jdbcTemplate.update("DELETE FROM audit_log");

        // Promote to target env
        String promoteJson = String.format("""
                {"targetEnvironment": "%s"}
                """, targetEnvSlug);
        ResponseEntity<String> promoteResponse = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/deployments/" + deploymentId + "/promote",
                HttpMethod.POST,
                new HttpEntity<>(promoteJson, authHeaders(aliceJwt)),
                String.class);
        assertThat(promoteResponse.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);

        Map<String, Object> row = queryAuditRow("promote_deployment");
        assertThat(row).isNotNull();
        assertThat(row.get("username")).isEqualTo("alice");
        assertThat(row.get("action")).isEqualTo("promote_deployment");
        assertThat(row.get("category")).isEqualTo("DEPLOYMENT");
        assertThat(row.get("result")).isEqualTo("SUCCESS");
        assertThat(row.get("target")).isNotNull();
        assertThat(row.get("target").toString()).isNotBlank();
    }

    @Test
    void deploy_with_unknown_appVersion_writes_FAILURE_audit_row() throws Exception {
        String unknownVersionId = UUID.randomUUID().toString();
        String json = String.format("""
                {"appVersionId": "%s"}
                """, unknownVersionId);

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/environments/default/apps/" + appSlug + "/deployments", HttpMethod.POST,
                new HttpEntity<>(json, authHeaders(aliceJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);

        Map<String, Object> row = queryAuditRow("deploy_app");
        assertThat(row).isNotNull();
        assertThat(row.get("username")).isEqualTo("alice");
        assertThat(row.get("action")).isEqualTo("deploy_app");
        assertThat(row.get("category")).isEqualTo("DEPLOYMENT");
        assertThat(row.get("result")).isEqualTo("FAILURE");
    }

    // ---- helpers ----

    private HttpHeaders authHeaders(String jwt) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + jwt);
        headers.set("X-Cameleer-Protocol-Version", "1");
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }

    private HttpHeaders authHeadersNoBody(String jwt) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + jwt);
        headers.set("X-Cameleer-Protocol-Version", "1");
        return headers;
    }

    /** Query the most recent audit_log row for the given action. Returns null if not found. */
    private Map<String, Object> queryAuditRow(String action) {
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT username, action, category, target, result FROM audit_log WHERE action = ? ORDER BY timestamp DESC LIMIT 1",
                action);
        return rows.isEmpty() ? null : rows.get(0);
    }
}
