package com.cameleer.server.app.outbound.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 OutboundConnectionAdminControllerIT extends AbstractPostgresIT {

    @Autowired private TestRestTemplate restTemplate;
    @Autowired private ObjectMapper objectMapper;
    @Autowired private TestSecurityHelper securityHelper;

    private String adminJwt;
    private String operatorJwt;
    private String viewerJwt;
    private com.github.tomakehurst.wiremock.WireMockServer wireMock;

    @org.junit.jupiter.api.AfterEach
    void tearDownWireMock() {
        if (wireMock != null) wireMock.stop();
    }

    @org.junit.jupiter.api.AfterEach
    void cleanupRows() {
        jdbcTemplate.update("DELETE FROM outbound_connections WHERE tenant_id = 'default'");
        // Clear deployments.created_by for our test users — sibling ITs
        // (DeploymentControllerIT etc.) may have left rows that FK-block user deletion.
        jdbcTemplate.update(
            "DELETE FROM deployments WHERE created_by IN ('test-admin','test-operator','test-viewer')");
        jdbcTemplate.update("DELETE FROM users WHERE user_id IN ('test-admin','test-operator','test-viewer')");
        // Clear the lifted license so later ITs see ABSENT state.
        securityHelper.clearTestLicense();
    }

    @BeforeEach
    void setUp() {
        adminJwt = securityHelper.adminToken();
        operatorJwt = securityHelper.operatorToken();
        viewerJwt = securityHelper.viewerToken();
        // Seed user rows matching the JWT subjects (users(user_id) is a FK target)
        seedUser("test-admin");
        seedUser("test-operator");
        seedUser("test-viewer");
        jdbcTemplate.update("DELETE FROM outbound_connections WHERE tenant_id = 'default'");
        // Lift the max_outbound_connections cap — duplicateNameReturns409 needs 2 creates,
        // and the default tier caps at 1. Other tests in this class don't exceed the cap
        // but lifting at the class level keeps the suite robust against ordering surprises.
        securityHelper.installSyntheticUnsignedLicense(java.util.Map.of("max_outbound_connections", 100));
    }

    private void seedUser(String userId) {
        jdbcTemplate.update(
                "INSERT INTO users (user_id, provider, email, display_name) VALUES (?, 'test', ?, ?) ON CONFLICT (user_id) DO NOTHING",
                userId, userId + "@example.com", userId);
    }

    private static final String CREATE_BODY = """
            {"name":"slack-ops","url":"https://hooks.slack.com/x","method":"POST",
             "tlsTrustMode":"SYSTEM_DEFAULT","auth":{}}""";

    @Test
    void adminCanCreate() throws Exception {
        ResponseEntity<String> resp = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(CREATE_BODY, securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        JsonNode body = objectMapper.readTree(resp.getBody());
        assertThat(body.path("name").asText()).isEqualTo("slack-ops");
        assertThat(body.path("hmacSecretSet").asBoolean()).isFalse();
        assertThat(body.path("id").asText()).isNotBlank();
    }

    @Test
    void operatorCannotCreate() {
        ResponseEntity<String> resp = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(CREATE_BODY, securityHelper.authHeaders(operatorJwt)),
                String.class);
        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }

    @Test
    void operatorCanList() {
        ResponseEntity<String> resp = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(operatorJwt)),
                String.class);
        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

    @Test
    void viewerCannotList() {
        ResponseEntity<String> resp = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
                String.class);
        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }

    @Test
    void nonHttpsUrlRejected() {
        String body = """
                {"name":"bad","url":"http://x","method":"POST",
                 "tlsTrustMode":"SYSTEM_DEFAULT","auth":{}}""";
        ResponseEntity<String> resp = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(body, securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }

    @Test
    void duplicateNameReturns409() {
        restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(CREATE_BODY, securityHelper.authHeaders(adminJwt)),
                String.class);
        ResponseEntity<String> dup = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(CREATE_BODY, securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(dup.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
    }

    @Test
    void deleteRemoves() throws Exception {
        ResponseEntity<String> create = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(CREATE_BODY, securityHelper.authHeaders(adminJwt)),
                String.class);
        String id = objectMapper.readTree(create.getBody()).path("id").asText();

        ResponseEntity<String> del = restTemplate.exchange(
                "/api/v1/admin/outbound-connections/" + id, HttpMethod.DELETE,
                new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
                String.class);
        assertThat(del.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);

        ResponseEntity<String> get = restTemplate.exchange(
                "/api/v1/admin/outbound-connections/" + id, HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
                String.class);
        assertThat(get.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
    }

    @Test
    void testActionReturnsStatusAndLatency() throws Exception {
        wireMock = new com.github.tomakehurst.wiremock.WireMockServer(
                com.github.tomakehurst.wiremock.core.WireMockConfiguration.options()
                        .httpDisabled(true).dynamicHttpsPort());
        wireMock.start();
        wireMock.stubFor(com.github.tomakehurst.wiremock.client.WireMock.post("/probe")
                .willReturn(com.github.tomakehurst.wiremock.client.WireMock.ok("pong")));

        String createBody = """
                {"name":"probe-target","url":"https://localhost:%d/probe","method":"POST",
                 "tlsTrustMode":"TRUST_ALL","auth":{}}""".formatted(wireMock.httpsPort());

        ResponseEntity<String> create = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(createBody, securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(create.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        String id = objectMapper.readTree(create.getBody()).path("id").asText();

        ResponseEntity<String> test = restTemplate.exchange(
                "/api/v1/admin/outbound-connections/" + id + "/test", HttpMethod.POST,
                new HttpEntity<>(securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(test.getStatusCode()).isEqualTo(HttpStatus.OK);
        JsonNode body = objectMapper.readTree(test.getBody());
        assertThat(body.path("status").asInt()).isEqualTo(200);
        assertThat(body.path("latencyMs").asLong()).isGreaterThanOrEqualTo(0);
        assertThat(body.path("tlsProtocol").asText()).isEqualTo("TLS");
        assertThat(body.path("error").isNull()).isTrue();
    }

    @Test
    void operatorCannotTest() throws Exception {
        ResponseEntity<String> create = restTemplate.exchange(
                "/api/v1/admin/outbound-connections", HttpMethod.POST,
                new HttpEntity<>(CREATE_BODY, securityHelper.authHeaders(adminJwt)),
                String.class);
        String id = objectMapper.readTree(create.getBody()).path("id").asText();

        ResponseEntity<String> test = restTemplate.exchange(
                "/api/v1/admin/outbound-connections/" + id + "/test", HttpMethod.POST,
                new HttpEntity<>(securityHelper.authHeaders(operatorJwt)),
                String.class);
        assertThat(test.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }
}
