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.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

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

class EnvironmentAdminControllerIT extends AbstractPostgresIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestSecurityHelper securityHelper;

    private String adminJwt;
    private String viewerJwt;
    private String operatorJwt;

    @BeforeEach
    void setUp() {
        adminJwt = securityHelper.adminToken();
        viewerJwt = securityHelper.viewerToken();
        operatorJwt = securityHelper.operatorToken();
        // Clean up test environments (keep default). Strip dependents first — sibling ITs
        // (e.g., DeploymentControllerAuditIT) may have left deployments/apps that FK back to
        // their non-default envs when the testcontainer is reused across runs.
        jdbcTemplate.update("DELETE FROM deployments");
        jdbcTemplate.update("DELETE FROM app_versions");
        jdbcTemplate.update("DELETE FROM apps");
        jdbcTemplate.update("DELETE FROM environments WHERE slug != 'default'");
        // Lift max_environments cap so existing IT scenarios that POST envs through the
        // controller succeed; the cap itself is exercised by EnvironmentCapEnforcementIT.
        securityHelper.installSyntheticUnsignedLicense(java.util.Map.of("max_environments", 100));
    }

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

    @Test
    void listEnvironments_asAdmin_returnsDefaultEnvironment() throws Exception {
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.isArray()).isTrue();
        assertThat(body.size()).isGreaterThanOrEqualTo(1);
        // Default environment should exist
        boolean hasDefault = false;
        for (JsonNode env : body) {
            if ("default".equals(env.path("slug").asText())) {
                hasDefault = true;
                break;
            }
        }
        assertThat(hasDefault).isTrue();
    }

    @Test
    void listEnvironments_asViewer_returns403() {
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }

    @Test
    void createEnvironment_asAdmin_returns201() throws Exception {
        String json = """
                {"slug": "staging", "displayName": "Staging", "production": false}
                """;

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(json, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.path("slug").asText()).isEqualTo("staging");
        assertThat(body.path("displayName").asText()).isEqualTo("Staging");
        assertThat(body.path("production").asBoolean()).isFalse();
        assertThat(body.path("enabled").asBoolean()).isTrue();
        assertThat(body.path("color").asText()).isEqualTo("slate");
        assertThat(body.has("id")).isTrue();
    }

    @Test
    void updateEnvironment_withValidColor_persists() throws Exception {
        restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>("""
                        {"slug": "color-ok", "displayName": "Color OK", "production": false}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments/color-ok", HttpMethod.PUT,
                new HttpEntity<>("""
                        {"displayName": "Color OK", "production": false, "enabled": true, "color": "amber"}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.path("color").asText()).isEqualTo("amber");
    }

    @Test
    void updateEnvironment_withNullColor_preservesExisting() throws Exception {
        restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>("""
                        {"slug": "color-preserve", "displayName": "Keep", "production": false}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);
        // Set color to teal
        restTemplate.exchange(
                "/api/v1/admin/environments/color-preserve", HttpMethod.PUT,
                new HttpEntity<>("""
                        {"displayName": "Keep", "production": false, "enabled": true, "color": "teal"}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);

        // Update without color field → teal preserved
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments/color-preserve", HttpMethod.PUT,
                new HttpEntity<>("""
                        {"displayName": "Still Keep", "production": false, "enabled": true}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.path("displayName").asText()).isEqualTo("Still Keep");
        assertThat(body.path("color").asText()).isEqualTo("teal");
    }

    @Test
    void updateEnvironment_withUnknownColor_returns400() throws Exception {
        restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>("""
                        {"slug": "color-bad", "displayName": "Bad", "production": false}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments/color-bad", HttpMethod.PUT,
                new HttpEntity<>("""
                        {"displayName": "Bad", "production": false, "enabled": true, "color": "neon"}
                        """, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }

    @Test
    void updateEnvironment_asAdmin_returns200() throws Exception {
        // Create an environment first
        String createJson = """
                {"slug": "update-test", "displayName": "Before", "production": false}
                """;
        restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(createJson, securityHelper.authHeaders(adminJwt)),
                String.class);

        // Update it by slug
        String updateJson = """
                {"displayName": "After", "production": true, "enabled": false}
                """;
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments/update-test", HttpMethod.PUT,
                new HttpEntity<>(updateJson, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.path("slug").asText()).isEqualTo("update-test");
        assertThat(body.path("displayName").asText()).isEqualTo("After");
        assertThat(body.path("production").asBoolean()).isTrue();
        assertThat(body.path("enabled").asBoolean()).isFalse();
    }

    @Test
    void updateEnvironment_withSlugInBody_ignoresSlug() throws Exception {
        String createJson = """
                {"slug": "slug-immutable-test", "displayName": "Original", "production": false}
                """;
        restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(createJson, securityHelper.authHeaders(adminJwt)),
                String.class);

        // Attempt to change slug via body — Jackson drops the unknown field
        String updateJson = """
                {"slug": "hacked", "displayName": "Renamed", "production": false, "enabled": true}
                """;
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments/slug-immutable-test", HttpMethod.PUT,
                new HttpEntity<>(updateJson, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.path("slug").asText()).isEqualTo("slug-immutable-test");
    }

    @Test
    void createEnvironment_invalidSlug_returns400() {
        String json = """
                {"slug": "Invalid Slug!", "displayName": "Bad"}
                """;
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(json, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }

    @Test
    void createEnvironment_duplicateSlug_returns400() {
        String json = """
                {"slug": "dup-test", "displayName": "Dup Test"}
                """;

        // First create should succeed
        restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(json, securityHelper.authHeaders(adminJwt)),
                String.class);

        // Second create with same slug should fail
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(json, securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }

    @Test
    void deleteEnvironment_defaultEnv_returns400() {
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments/default", HttpMethod.DELETE,
                new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }

    @Test
    void createEnvironment_asOperator_returns403() {
        String json = """
                {"slug": "op-test", "displayName": "Op Test"}
                """;

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/admin/environments", HttpMethod.POST,
                new HttpEntity<>(json, securityHelper.authHeaders(operatorJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }
}
