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 java.util.UUID;

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

class AgentCommandControllerIT extends AbstractPostgresIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestSecurityHelper securityHelper;

    private String agentJwt;
    private String operatorJwt;

    @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));
        agentJwt = securityHelper.registerTestAgent("test-agent-command-it");
        operatorJwt = securityHelper.operatorToken();
    }

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

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

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

    @Test
    void sendCommandToAgent_returns202WithCommandId() throws Exception {
        String agentId = "cmd-it-single-" + UUID.randomUUID().toString().substring(0, 8);
        registerAgent(agentId, "Command Agent", "test-group");

        String commandJson = """
                {"type": "config-update", "payload": {"key": "value"}}
                """;

        ResponseEntity<String> response = restTemplate.postForEntity(
                "/api/v1/agents/" + agentId + "/commands",
                new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
                String.class);

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

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.has("commandId")).isTrue();
        assertThat(body.get("commandId").asText()).isNotBlank();
        assertThat(body.get("status").asText()).isIn("PENDING", "DELIVERED");
    }

    @Test
    void sendGroupCommand_returns200WithAggregateReplies() throws Exception {
        String group = "cmd-it-group-" + UUID.randomUUID().toString().substring(0, 8);
        registerAgent("agent-g1-" + group, "Group Agent 1", group);
        registerAgent("agent-g2-" + group, "Group Agent 2", group);

        String commandJson = """
                {"type": "deep-trace", "payload": {"correlationId": "group-trace-1"}}
                """;

        // Group dispatch is synchronous request-reply with a 10s deadline; returns
        // 200 with the aggregated reply set (total/responded/timedOut). Neither agent
        // holds an SSE connection in this test, so both time out but are counted.
        ResponseEntity<String> response = restTemplate.postForEntity(
                "/api/v1/agents/groups/" + group + "/commands",
                new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
                String.class);

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

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.get("total").asInt()).isEqualTo(2);
        assertThat(body.get("timedOut").isArray()).isTrue();
        assertThat(body.get("timedOut").size()).isEqualTo(2);
    }

    @Test
    void broadcastCommand_returns202WithLiveAgentCount() throws Exception {
        String agentId = "cmd-it-broadcast-" + UUID.randomUUID().toString().substring(0, 8);
        registerAgent(agentId, "Broadcast Agent", "broadcast-group");

        String commandJson = """
                {"type": "replay", "payload": {"exchangeId": "ex-broadcast"}}
                """;

        ResponseEntity<String> response = restTemplate.postForEntity(
                "/api/v1/agents/commands",
                new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
                String.class);

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

        JsonNode body = objectMapper.readTree(response.getBody());
        assertThat(body.get("targetCount").asInt()).isGreaterThanOrEqualTo(1);
        assertThat(body.get("commandIds").isArray()).isTrue();
    }

    @Test
    void acknowledgeCommand_returns200() throws Exception {
        String agentId = "cmd-it-ack-" + UUID.randomUUID().toString().substring(0, 8);
        registerAgent(agentId, "Ack Agent", "test-group");

        String commandJson = """
                {"type": "config-update", "payload": {"key": "ack-test"}}
                """;

        ResponseEntity<String> cmdResponse = restTemplate.postForEntity(
                "/api/v1/agents/" + agentId + "/commands",
                new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
                String.class);

        JsonNode cmdBody = objectMapper.readTree(cmdResponse.getBody());
        String commandId = cmdBody.get("commandId").asText();

        ResponseEntity<Void> ackResponse = restTemplate.exchange(
                "/api/v1/agents/" + agentId + "/commands/" + commandId + "/ack",
                HttpMethod.POST,
                new HttpEntity<>(securityHelper.authHeadersNoBody(agentJwt)),
                Void.class);

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

    @Test
    void acknowledgeUnknownCommand_returns404() {
        String agentId = "cmd-it-ack-unknown-" + UUID.randomUUID().toString().substring(0, 8);
        registerAgent(agentId, "Ack Unknown Agent", "test-group");

        ResponseEntity<Void> response = restTemplate.exchange(
                "/api/v1/agents/" + agentId + "/commands/nonexistent-cmd-id/ack",
                HttpMethod.POST,
                new HttpEntity<>(securityHelper.authHeadersNoBody(agentJwt)),
                Void.class);

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

    @Test
    void sendCommandToUnregisteredAgent_returns404() {
        String commandJson = """
                {"type": "config-update", "payload": {"key": "value"}}
                """;

        ResponseEntity<String> response = restTemplate.postForEntity(
                "/api/v1/agents/nonexistent-agent-xyz/commands",
                new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
                String.class);

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