package com.cameleer.server.app.alerting;

import com.cameleer.server.app.AbstractPostgresIT;
import com.cameleer.server.app.TestSecurityHelper;
import com.cameleer.server.app.search.ClickHouseLogStore;
import com.cameleer.server.core.agent.AgentRegistryService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

/**
 * Verifies the outbound connection allowed-environment guard end-to-end:
 * <ol>
 *   <li>Rule in env-B referencing a connection restricted to env-A → 422.</li>
 *   <li>Rule in env-A referencing the same connection → 201.</li>
 *   <li>Narrowing the connection's allowed envs to env-C (removing env-A) while
 *       a rule in env-A still references it → 409 via PUT /admin/outbound-connections/{id}.</li>
 * </ol>
 */
class OutboundConnectionAllowedEnvIT extends AbstractPostgresIT {

    @MockBean AgentRegistryService agentRegistryService;
    @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;

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

    @Value("${cameleer.server.tenant.id:default}")
    private String tenantId;

    private String adminJwt;
    private String operatorJwt;

    private UUID envIdA;
    private UUID envIdB;
    private UUID envIdC;
    private String envSlugA;
    private String envSlugB;
    private UUID connId;

    @BeforeEach
    void setUp() throws Exception {
        when(agentRegistryService.findAll()).thenReturn(List.of());

        adminJwt    = securityHelper.adminToken();
        operatorJwt = securityHelper.operatorToken();

        jdbcTemplate.update("INSERT INTO users (user_id, provider, email) VALUES ('test-admin', 'test', 'adm@test.lc') ON CONFLICT (user_id) DO NOTHING");
        jdbcTemplate.update("INSERT INTO users (user_id, provider, email) VALUES ('test-operator', 'test', 'op@test.lc') ON CONFLICT (user_id) DO NOTHING");

        envSlugA = "conn-env-a-" + UUID.randomUUID().toString().substring(0, 6);
        envSlugB = "conn-env-b-" + UUID.randomUUID().toString().substring(0, 6);
        String envSlugC = "conn-env-c-" + UUID.randomUUID().toString().substring(0, 6);
        envIdA = UUID.randomUUID();
        envIdB = UUID.randomUUID();
        envIdC = UUID.randomUUID();

        jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdA, envSlugA, "A");
        jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdB, envSlugB, "B");
        jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdC, envSlugC, "C");

        // Create outbound connection restricted to env-A
        String connBody = objectMapper.writeValueAsString(java.util.Map.of(
            "name", "env-a-only-conn-" + UUID.randomUUID().toString().substring(0, 6),
            "url", "https://httpbin.org/post",
            "method", "POST",
            "tlsTrustMode", "SYSTEM_DEFAULT",
            "auth", java.util.Map.of(),
            "allowedEnvironmentIds", List.of(envIdA.toString())
        ));
        ResponseEntity<String> connResp = restTemplate.exchange(
            "/api/v1/admin/outbound-connections",
            HttpMethod.POST,
            new HttpEntity<>(connBody, securityHelper.authHeaders(adminJwt)),
            String.class);
        assertThat(connResp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        connId = UUID.fromString(objectMapper.readTree(connResp.getBody()).path("id").asText());
    }

    @AfterEach
    void cleanUp() {
        jdbcTemplate.update("DELETE FROM alert_rules WHERE environment_id IN (?, ?, ?)", envIdA, envIdB, envIdC);
        jdbcTemplate.update("DELETE FROM outbound_connections WHERE id = ?", connId);
        jdbcTemplate.update("DELETE FROM environments WHERE id IN (?, ?, ?)", envIdA, envIdB, envIdC);
        jdbcTemplate.update("DELETE FROM users WHERE user_id IN ('test-admin', 'test-operator')");
    }

    @Test
    void ruleInEnvB_referencingEnvAOnlyConnection_returns422() {
        String body = ruleBodyWithConnection("envb-rule", connId);
        ResponseEntity<String> resp = restTemplate.exchange(
            "/api/v1/environments/" + envSlugB + "/alerts/rules",
            HttpMethod.POST,
            new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
            String.class);

        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY);
    }

    @Test
    void ruleInEnvA_referencingEnvAOnlyConnection_returns201() throws Exception {
        String body = ruleBodyWithConnection("enva-rule", connId);
        ResponseEntity<String> resp = restTemplate.exchange(
            "/api/v1/environments/" + envSlugA + "/alerts/rules",
            HttpMethod.POST,
            new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
            String.class);

        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
    }

    @Test
    void narrowingConnectionToEnvC_whileRuleInEnvA_references_returns409() throws Exception {
        // First create a rule in env-A that references the connection
        String ruleBody = ruleBodyWithConnection("narrowing-guard-rule", connId);
        ResponseEntity<String> ruleResp = restTemplate.exchange(
            "/api/v1/environments/" + envSlugA + "/alerts/rules",
            HttpMethod.POST,
            new HttpEntity<>(ruleBody, securityHelper.authHeaders(operatorJwt)),
            String.class);
        assertThat(ruleResp.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        // Now update the connection to only allow env-C (removing env-A)
        String updateBody = objectMapper.writeValueAsString(java.util.Map.of(
            "name", "env-a-only-conn-narrowed",
            "url", "https://httpbin.org/post",
            "method", "POST",
            "tlsTrustMode", "SYSTEM_DEFAULT",
            "auth", java.util.Map.of(),
            "allowedEnvironmentIds", List.of(envIdC.toString())   // removed env-A
        ));

        ResponseEntity<String> updateResp = restTemplate.exchange(
            "/api/v1/admin/outbound-connections/" + connId,
            HttpMethod.PUT,
            new HttpEntity<>(updateBody, securityHelper.authHeaders(adminJwt)),
            String.class);

        // The guard should fire: env-A was removed but a rule in env-A still references it
        assertThat(updateResp.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
    }

    // ─────────────────────────────────────────────────────────────────────────

    private static String ruleBodyWithConnection(String name, UUID connectionId) {
        return """
            {"name":"%s","severity":"WARNING","conditionKind":"ROUTE_METRIC",
             "condition":{"kind":"ROUTE_METRIC","scope":{},
               "metric":"ERROR_RATE","comparator":"GT","threshold":0.05,"windowSeconds":60},
             "webhooks":[{"outboundConnectionId":"%s"}]}
            """.formatted(name, connectionId);
    }
}
