package com.cameleer.server.app.controller;

import com.cameleer.server.app.dto.AgentInstanceResponse;
import com.cameleer.server.app.dto.ErrorResponse;
import com.cameleer.server.app.web.EnvPath;
import com.cameleer.server.core.agent.AgentInfo;
import com.cameleer.server.core.agent.AgentRegistryService;
import com.cameleer.server.core.agent.AgentState;
import com.cameleer.server.core.runtime.Environment;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Read-only user-facing list of agents in an environment. Agent self-service
 * endpoints (register/heartbeat/refresh/deregister/events/commands) remain
 * flat at /api/v1/agents/... — those are JWT-authoritative and env is
 * derived from the token.
 */
@RestController
@RequestMapping("/api/v1/environments/{envSlug}/agents")
@Tag(name = "Agent List", description = "List registered agents in an environment")
public class AgentListController {

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

    private final AgentRegistryService registryService;
    private final JdbcTemplate jdbc;

    public AgentListController(AgentRegistryService registryService,
                                @org.springframework.beans.factory.annotation.Qualifier("clickHouseJdbcTemplate") JdbcTemplate jdbc) {
        this.registryService = registryService;
        this.jdbc = jdbc;
    }

    @GetMapping
    @Operation(summary = "List all agents in this environment",
            description = "Returns registered agents with runtime metrics, optionally filtered by status and/or application")
    @ApiResponse(responseCode = "200", description = "Agent list returned")
    @ApiResponse(responseCode = "400", description = "Invalid status filter",
            content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
    public ResponseEntity<List<AgentInstanceResponse>> listAgents(
            @EnvPath Environment env,
            @RequestParam(required = false) String status,
            @RequestParam(required = false) String application) {
        List<AgentInfo> agents;

        if (status != null) {
            try {
                AgentState stateFilter = AgentState.valueOf(status.toUpperCase());
                agents = registryService.findByState(stateFilter);
            } catch (IllegalArgumentException e) {
                return ResponseEntity.badRequest().build();
            }
        } else {
            agents = registryService.findAll();
        }

        // Filter by env (from path — always applied)
        agents = agents.stream()
                .filter(a -> env.slug().equals(a.environmentId()))
                .toList();

        if (application != null && !application.isBlank()) {
            agents = agents.stream()
                    .filter(a -> application.equals(a.applicationId()))
                    .toList();
        }

        Map<String, double[]> agentMetrics = queryAgentMetrics();
        Map<String, Double> cpuByInstance = queryAgentCpuUsage();
        final List<AgentInfo> finalAgents = agents;

        List<AgentInstanceResponse> response = finalAgents.stream()
                .map(a -> {
                    AgentInstanceResponse dto = AgentInstanceResponse.from(a);
                    double[] m = agentMetrics.get(a.applicationId());
                    if (m != null) {
                        long appAgentCount = finalAgents.stream()
                                .filter(ag -> ag.applicationId().equals(a.applicationId())).count();
                        double agentTps = appAgentCount > 0 ? m[0] / appAgentCount : 0;
                        double errorRate = m[1];
                        int activeRoutes = (int) m[2];
                        dto = dto.withMetrics(agentTps, errorRate, activeRoutes);
                    }
                    Double cpu = cpuByInstance.get(a.instanceId());
                    if (cpu != null) {
                        dto = dto.withCpuUsage(cpu);
                    }
                    return dto;
                })
                .toList();
        return ResponseEntity.ok(response);
    }

    private Map<String, double[]> queryAgentMetrics() {
        Map<String, double[]> result = new HashMap<>();
        Instant now = Instant.now();
        Instant from1m = now.minus(1, ChronoUnit.MINUTES);
        try {
            jdbc.query(
                    "SELECT application_id, " +
                            "uniqMerge(total_count) AS total, " +
                            "uniqIfMerge(failed_count) AS failed, " +
                            "COUNT(DISTINCT route_id) AS active_routes " +
                            "FROM stats_1m_route WHERE bucket >= " + lit(from1m) + " AND bucket < " + lit(now) +
                            " GROUP BY application_id",
                    rs -> {
                        long total = rs.getLong("total");
                        long failed = rs.getLong("failed");
                        double tps = total / 60.0;
                        double errorRate = total > 0 ? (double) failed / total : 0.0;
                        int activeRoutes = rs.getInt("active_routes");
                        result.put(rs.getString("application_id"), new double[]{tps, errorRate, activeRoutes});
                    });
        } catch (Exception e) {
            log.debug("Could not query agent metrics: {}", e.getMessage());
        }
        return result;
    }

    private Map<String, Double> queryAgentCpuUsage() {
        Map<String, Double> result = new HashMap<>();
        Instant now = Instant.now();
        Instant from2m = now.minus(2, ChronoUnit.MINUTES);
        try {
            jdbc.query(
                    "SELECT instance_id, avg(metric_value) AS cpu_avg " +
                            "FROM agent_metrics " +
                            "WHERE metric_name = 'process.cpu.usage.value'" +
                            " AND collected_at >= " + lit(from2m) + " AND collected_at < " + lit(now) +
                            " GROUP BY instance_id",
                    rs -> {
                        result.put(rs.getString("instance_id"), rs.getDouble("cpu_avg"));
                    });
        } catch (Exception e) {
            log.debug("Could not query agent CPU usage: {}", e.getMessage());
        }
        return result;
    }

    private static String lit(Instant instant) {
        return "'" + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
                .withZone(java.time.ZoneOffset.UTC)
                .format(instant.truncatedTo(ChronoUnit.SECONDS)) + "'";
    }
}
