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.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;

class AgentRegistrationControllerIT extends AbstractPostgresIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestSecurityHelper securityHelper;

    private String jwt;
    private String viewerJwt;

    @BeforeEach
    void setUp() {
        // Lift max_agents cap so this IT (which registers many agents per test) isn't gated
        // by license enforcement. Cap behaviour itself is exercised by AgentCapEnforcementIT.
        securityHelper.installSyntheticUnsignedLicense(Map.of("max_agents", 100));
        jwt = securityHelper.registerTestAgent("test-agent-registration-it");
        viewerJwt = securityHelper.viewerToken();
    }

    @AfterEach
    void tearDown() {
        securityHelper.clearTestLicense();
    }

    private ResponseEntity<String> registerAgent(String agentId, String name) {
        String json = """
                {
                  "instanceId": "%s",
                  "applicationId": "test-group",
                  "environmentId": "default",
                  "version": "1.0.0",
                  "routeIds": ["route-1", "route-2"],
                  "capabilities": {"tracing": true}
                }
                """.formatted(agentId);

        return restTemplate.postForEntity(
                "/api/v1/agents/register",
                new HttpEntity<>(json, securityHelper.bootstrapHeaders()),
                String.class);
    }

    @Test
    void registerNewAgent_returns200WithAgentIdAndSseEndpoint() throws Exception {
        ResponseEntity<String> response = registerAgent("agent-it-1", "IT Agent 1");

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.get("instanceId").asText()).isEqualTo("agent-it-1");
        // Controller returns an absolute URL via ServletUriComponentsBuilder.fromCurrentContextPath(),
        // so only assert the path suffix — the host:port varies per RANDOM_PORT test run.
        assertThat(body.get("sseEndpoint").asText()).endsWith("/api/v1/agents/agent-it-1/events");
        assertThat(body.get("heartbeatIntervalMs").asLong()).isGreaterThan(0);
        assertThat(body.has("serverPublicKey")).isTrue();
        assertThat(body.get("serverPublicKey").asText()).isNotEmpty();
        assertThat(body.has("accessToken")).isTrue();
        assertThat(body.get("accessToken").asText()).isNotEmpty();
        assertThat(body.has("refreshToken")).isTrue();
        assertThat(body.get("refreshToken").asText()).isNotEmpty();
    }

    @Test
    void reRegisterSameAgent_returns200WithLiveState() throws Exception {
        registerAgent("agent-it-reregister", "First Registration");

        ResponseEntity<String> response = registerAgent("agent-it-reregister", "Second Registration");

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.get("instanceId").asText()).isEqualTo("agent-it-reregister");
    }

    @Test
    void heartbeatKnownAgent_returns200() {
        registerAgent("agent-it-hb", "HB Agent");

        ResponseEntity<Void> response = restTemplate.exchange(
                "/api/v1/agents/agent-it-hb/heartbeat",
                HttpMethod.POST,
                new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
                Void.class);

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

    @Test
    void heartbeatUnknownAgent_autoHealsFromJwtEnv_returns200() {
        // Post-fb54f9cb: heartbeat for an agent not in the registry auto-heals
        // from the JWT env claim + heartbeat body (covers agent-side survival of
        // server restarts). The no-registry 404 branch is only reachable without
        // a JWT, which Spring Security rejects at the filter chain before the
        // controller sees the request. See CLAUDE.md "Auto-heals from JWT env
        // claim + heartbeat body on heartbeat/SSE after server restart".
        ResponseEntity<Void> response = restTemplate.exchange(
                "/api/v1/agents/unknown-agent-xyz/heartbeat",
                HttpMethod.POST,
                new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
                Void.class);

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

    @Test
    void listAllAgents_returnsBothAgents() throws Exception {
        registerAgent("agent-it-list-1", "List Agent 1");
        registerAgent("agent-it-list-2", "List Agent 2");

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/environments/default/agents",
                HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.isArray()).isTrue();
        assertThat(body.size()).isGreaterThanOrEqualTo(2);
    }

    @Test
    void listAgentsByStatus_filtersCorrectly() throws Exception {
        registerAgent("agent-it-filter", "Filter Agent");

        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/environments/default/agents?status=LIVE",
                HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
                String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.isArray()).isTrue();
        for (JsonNode agent : body) {
            assertThat(agent.get("status").asText()).isEqualTo("LIVE");
        }
    }

    @Test
    void listAgentsWithInvalidStatus_returns400() {
        ResponseEntity<String> response = restTemplate.exchange(
                "/api/v1/environments/default/agents?status=INVALID",
                HttpMethod.GET,
                new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
                String.class);

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