package io.cameleer.server.app.license;

import io.cameleer.server.app.AbstractPostgresIT;
import io.cameleer.server.app.TestSecurityHelper;
import io.cameleer.server.app.search.ClickHouseLogStore;
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.boot.test.mock.mockito.MockBean;
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;

/**
 * Verifies that the {@code max_alert_rules} cap from the default tier is enforced at
 * {@code POST /api/v1/environments/{envSlug}/alerts/rules}. Default tier
 * {@code max_alert_rules = 2}; with no license installed the gate is in
 * {@link io.cameleer.license.LicenseState#ABSENT} and the defaults are
 * authoritative. The first two creates succeed; the third must be rejected with the
 * structured 403 envelope produced by {@link LicenseExceptionAdvice}.
 */
class AlertRuleCapEnforcementIT extends AbstractPostgresIT {

    // ExchangeMatchEvaluator and LogPatternEvaluator depend on the concrete CH log store
    // bean. Mock it so the Spring context wires up without real ClickHouse log behaviour.
    @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestSecurityHelper securityHelper;

    private String adminJwt;
    private String envSlug;

    @BeforeEach
    void setUp() {
        adminJwt = securityHelper.adminToken();
        // Defensive: a sibling IT may have left a license installed (LicenseGate is a singleton
        // per Spring context; @SpringBootTest reuses contexts across ITs).
        securityHelper.clearTestLicense();
        // Strip alert-rule dependents first, then the rules themselves — the cap is per-tenant
        // (tenant-wide count, not env-scoped).
        jdbcTemplate.update("DELETE FROM alert_notifications");
        jdbcTemplate.update("DELETE FROM alert_instances");
        jdbcTemplate.update("DELETE FROM alert_silences");
        jdbcTemplate.update("DELETE FROM alert_rule_targets");
        jdbcTemplate.update("DELETE FROM alert_rules");
        // Seed user row for the JWT subject — alert_rules.created_by FKs to users(user_id).
        jdbcTemplate.update(
                "INSERT INTO users (user_id, provider, email, display_name) VALUES (?, 'test', ?, ?) ON CONFLICT (user_id) DO NOTHING",
                "test-admin", "test-admin@example.com", "test-admin");
        // Use the seeded "default" environment.
        envSlug = "default";
    }

    @AfterEach
    void tearDown() {
        // Defensive cleanup — we never installed a license, but make sure later ITs see ABSENT.
        securityHelper.clearTestLicense();
        jdbcTemplate.update("DELETE FROM alert_notifications");
        jdbcTemplate.update("DELETE FROM alert_instances");
        jdbcTemplate.update("DELETE FROM alert_silences");
        jdbcTemplate.update("DELETE FROM alert_rule_targets");
        jdbcTemplate.update("DELETE FROM alert_rules");
        jdbcTemplate.update("DELETE FROM users WHERE user_id = 'test-admin'");
    }

    @Test
    void createBeyondCap_returns403WithStateAndMessage() throws Exception {
        // Default tier: max_alert_rules = 2. First two creates succeed; the third rejects.
        ResponseEntity<String> first = restTemplate.exchange(
                "/api/v1/environments/" + envSlug + "/alerts/rules", HttpMethod.POST,
                new HttpEntity<>(ruleBody("rule-1"), securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(first.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        ResponseEntity<String> second = restTemplate.exchange(
                "/api/v1/environments/" + envSlug + "/alerts/rules", HttpMethod.POST,
                new HttpEntity<>(ruleBody("rule-2"), securityHelper.authHeaders(adminJwt)),
                String.class);
        assertThat(second.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/environments/" + envSlug + "/alerts/rules", HttpMethod.POST,
                new HttpEntity<>(ruleBody("rule-3"), securityHelper.authHeaders(adminJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.path("error").asText()).isEqualTo("license cap reached");
        assertThat(body.path("limit").asText()).isEqualTo("max_alert_rules");
        assertThat(body.path("cap").asInt()).isEqualTo(2);
        assertThat(body.path("state").asText()).isEqualTo("ABSENT");
        assertThat(body.has("message")).isTrue();
        assertThat(body.path("message").asText()).isNotBlank();

        // And the third rule was NOT persisted.
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM alert_rules WHERE name = 'rule-3'", Integer.class);
        assertThat(count).isZero();

        // Total rules still 2 — the rejection short-circuited before any insert.
        Integer total = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM alert_rules", Integer.class);
        assertThat(total).isEqualTo(2);
    }

    /**
     * Minimal valid alert-rule request body: a ROUTE_METRIC condition with a USER target so
     * the controller's "at least one webhook or target" guard passes. The rule is otherwise
     * inert — it does not need to evaluate or fire to exercise the license cap.
     */
    private static String ruleBody(String name) {
        return """
                {"name":"%s","severity":"WARNING","conditionKind":"ROUTE_METRIC",
                 "condition":{"kind":"ROUTE_METRIC","scope":{},
                   "metric":"ERROR_RATE","comparator":"GT","threshold":0.05,"windowSeconds":60},
                 "targets":[{"kind":"USER","targetId":"test-admin"}]}
                """.formatted(name);
    }
}
