package com.cameleer.server.app.license;

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

import java.util.Map;

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

/**
 * Verifies that the {@code max_users} cap is enforced at
 * {@code POST /api/v1/admin/users}. The IT installs a synthetic license that lowers the cap to
 * {@code 2} so the rejection lands on a small number of HTTP calls. The structured 403 envelope
 * is produced by {@link LicenseExceptionAdvice}; the {@code cap_exceeded} audit row is written
 * by {@link LicenseEnforcer} when its 3-arg ctor wires {@code AuditService} (T16).
 */
class UserCapEnforcementIT extends AbstractPostgresIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestSecurityHelper securityHelper;

    private String adminJwt;

    @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 user_roles (FK to users) before users themselves.
        jdbcTemplate.update("DELETE FROM user_roles");
        jdbcTemplate.update("DELETE FROM user_groups");
        jdbcTemplate.update("DELETE FROM users");
        // Lower max_users to 2 so the cap rejection lands on the third create call.
        securityHelper.installSyntheticUnsignedLicense(Map.of("max_users", 2));
        // Clear stale audit rows so the cap_exceeded assertion is unambiguous.
        jdbcTemplate.update("DELETE FROM audit_log WHERE category = 'LICENSE'");
    }

    @AfterEach
    void tearDown() {
        securityHelper.clearTestLicense();
        jdbcTemplate.update("DELETE FROM user_roles");
        jdbcTemplate.update("DELETE FROM user_groups");
        jdbcTemplate.update("DELETE FROM users");
    }

    private ResponseEntity<String> createUser(String username) {
        // Password meets the policy (12+ chars, 3-of-4 character classes, doesn't match username).
        String body = """
                {
                  "username": "%s",
                  "displayName": "User %s",
                  "email": "%s@example.com",
                  "password": "Sup3rSecret-Pass!"
                }
                """.formatted(username, username, username);
        return restTemplate.exchange(
                "/api/v1/admin/users", HttpMethod.POST,
                new HttpEntity<>(body, securityHelper.authHeaders(adminJwt)),
                String.class);
    }

    @Test
    void createBeyondCap_returns403WithStateAndMessage() throws Exception {
        // Synthetic license: max_users = 2. Two creates succeed; the third rejects.
        assertThat(createUser("alice").getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(createUser("bob").getStatusCode()).isEqualTo(HttpStatus.OK);

        ResponseEntity<String> third = createUser("charlie");
        assertThat(third.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);

        JsonNode body = objectMapper.readTree(third.getBody());
        assertThat(body.path("error").asText()).isEqualTo("license cap reached");
        assertThat(body.path("limit").asText()).isEqualTo("max_users");
        assertThat(body.path("cap").asInt()).isEqualTo(2);
        // We installed a synthetic license, so the gate is ACTIVE.
        assertThat(body.path("state").asText()).isEqualTo("ACTIVE");
        assertThat(body.has("message")).isTrue();
        assertThat(body.path("message").asText()).isNotBlank();

        // The third user was NOT persisted.
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM users WHERE user_id = 'charlie'", Integer.class);
        assertThat(count).isZero();

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

        // LicenseEnforcer (3-arg ctor from T16) wrote the cap_exceeded audit row.
        Integer auditCount = jdbcTemplate.queryForObject("""
                SELECT COUNT(*) FROM audit_log
                WHERE category = 'LICENSE'
                  AND action = 'cap_exceeded'
                  AND target = 'max_users'
                  AND result = 'FAILURE'
                """, Integer.class);
        assertThat(auditCount).isEqualTo(1);
    }
}
