package com.cameleer.server.app.controller;

import com.cameleer.server.app.agent.SseConnectionManager;
import com.cameleer.server.app.security.JwtAuthenticationFilter;
import com.cameleer.server.core.agent.AgentInfo;
import com.cameleer.server.core.agent.AgentRegistryService;
import com.cameleer.server.core.security.JwtService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;
import java.util.Map;

/**
 * SSE endpoint for real-time event streaming to agents.
 * <p>
 * Agents connect to {@code GET /api/v1/agents/{id}/events} to receive
 * config-update, deep-trace, and replay commands as Server-Sent Events.
 */
@RestController
@RequestMapping("/api/v1/agents")
@Tag(name = "Agent SSE", description = "Server-Sent Events endpoint for agent communication")
public class AgentSseController {

    private static final Logger log = LoggerFactory.getLogger(AgentSseController.class);

    private final SseConnectionManager connectionManager;
    private final AgentRegistryService registryService;

    public AgentSseController(SseConnectionManager connectionManager,
                               AgentRegistryService registryService) {
        this.connectionManager = connectionManager;
        this.registryService = registryService;
    }

    @GetMapping(value = "/{id}/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @Operation(summary = "Open SSE event stream",
            description = "Opens a Server-Sent Events stream for the specified agent. "
                    + "Commands (config-update, deep-trace, replay) are pushed as events. "
                    + "Ping keepalive comments sent every 15 seconds.")
    @ApiResponse(responseCode = "200", description = "SSE stream opened")
    @ApiResponse(responseCode = "404", description = "Agent not registered and cannot be auto-registered")
    public SseEmitter events(
            @PathVariable String id,
            @Parameter(description = "Last received event ID (no replay, acknowledged only)")
            @RequestHeader(value = "Last-Event-ID", required = false) String lastEventId,
            HttpServletRequest httpRequest) {

        AgentInfo agent = registryService.findById(id);
        if (agent == null) {
            // Auto-heal re-registers an agent from JWT claims after a server
            // restart, but only when the JWT subject matches the path id.
            // Otherwise a holder of any valid agent JWT could spoof an
            // arbitrary agentId in the URL.
            var jwtResult = (JwtService.JwtValidationResult) httpRequest.getAttribute(
                    JwtAuthenticationFilter.JWT_RESULT_ATTR);
            if (jwtResult != null && id.equals(jwtResult.subject())) {
                String application = jwtResult.application() != null ? jwtResult.application() : "default";
                String env = jwtResult.environment() != null ? jwtResult.environment() : "default";
                registryService.register(id, id, application, env, "unknown", List.of(), Map.of());
                log.info("Auto-registered agent {} (app={}, env={}) from SSE connect after server restart", id, application, env);
            } else {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Agent not found: " + id);
            }
        }

        if (lastEventId != null) {
            log.debug("Agent {} reconnecting with Last-Event-ID: {} (no replay)", id, lastEventId);
        }

        return connectionManager.connect(id);
    }
}
