package com.cameleer.server.app.controller;

import com.cameleer.server.app.dto.AgentSummary;
import com.cameleer.server.app.dto.AppCatalogEntry;
import com.cameleer.server.app.dto.RouteSummary;
import com.cameleer.server.app.web.EnvPath;
import com.cameleer.common.graph.RouteGraph;
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.agent.RouteStateRegistry;
import com.cameleer.server.core.runtime.Environment;
import com.cameleer.server.core.storage.DiagramStore;
import com.cameleer.server.core.storage.RouteCatalogEntry;
import com.cameleer.server.core.storage.RouteCatalogStore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
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.sql.Timestamp;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/v1/environments/{envSlug}")
@Tag(name = "Route Catalog", description = "Route catalog and discovery (env-scoped)")
public class RouteCatalogController {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RouteCatalogController.class);

    private final AgentRegistryService registryService;
    private final DiagramStore diagramStore;
    private final JdbcTemplate jdbc;
    private final RouteStateRegistry routeStateRegistry;
    private final RouteCatalogStore routeCatalogStore;

    public RouteCatalogController(AgentRegistryService registryService,
                                   DiagramStore diagramStore,
                                   @org.springframework.beans.factory.annotation.Qualifier("clickHouseJdbcTemplate") JdbcTemplate jdbc,
                                   RouteStateRegistry routeStateRegistry,
                                   RouteCatalogStore routeCatalogStore) {
        this.registryService = registryService;
        this.diagramStore = diagramStore;
        this.jdbc = jdbc;
        this.routeStateRegistry = routeStateRegistry;
        this.routeCatalogStore = routeCatalogStore;
    }

    @GetMapping("/routes")
    @Operation(summary = "Get route catalog for this environment",
            description = "Returns all applications with their routes, agents, and health status — filtered to this environment")
    @ApiResponse(responseCode = "200", description = "Catalog returned")
    public ResponseEntity<List<AppCatalogEntry>> getCatalog(
            @EnvPath Environment env,
            @RequestParam(required = false) String from,
            @RequestParam(required = false) String to) {
        String envSlug = env.slug();
        List<AgentInfo> allAgents = registryService.findAll().stream()
                .filter(a -> envSlug.equals(a.environmentId()))
                .toList();

        Map<String, List<AgentInfo>> agentsByApp = allAgents.stream()
                .collect(Collectors.groupingBy(AgentInfo::applicationId, LinkedHashMap::new, Collectors.toList()));

        Map<String, Set<String>> routesByApp = new LinkedHashMap<>();
        for (var entry : agentsByApp.entrySet()) {
            Set<String> routes = new LinkedHashSet<>();
            for (AgentInfo agent : entry.getValue()) {
                if (agent.routeIds() != null) {
                    routes.addAll(agent.routeIds());
                }
            }
            routesByApp.put(entry.getKey(), routes);
        }

        Instant now = Instant.now();
        Instant rangeFrom = from != null ? Instant.parse(from) : now.minus(24, ChronoUnit.HOURS);
        Instant rangeTo = to != null ? Instant.parse(to) : now;
        Map<String, Long> routeExchangeCounts = new LinkedHashMap<>();
        Map<String, Instant> routeLastSeen = new LinkedHashMap<>();
        try {
            jdbc.query(
                    "SELECT application_id, route_id, uniqMerge(total_count) AS cnt, MAX(bucket) AS last_seen " +
                            "FROM stats_1m_route WHERE bucket >= " + lit(rangeFrom) + " AND bucket < " + lit(rangeTo) +
                            " AND environment = " + lit(envSlug) +
                            " GROUP BY application_id, route_id",
                    rs -> {
                        String key = rs.getString("application_id") + "/" + rs.getString("route_id");
                        routeExchangeCounts.put(key, rs.getLong("cnt"));
                        Timestamp ts = rs.getTimestamp("last_seen");
                        if (ts != null) routeLastSeen.put(key, ts.toInstant());
                    });
        } catch (Exception e) {
            log.warn("Failed to query route exchange counts: {}", e.getMessage());
        }

        for (var countEntry : routeExchangeCounts.entrySet()) {
            String[] parts = countEntry.getKey().split("/", 2);
            if (parts.length == 2) {
                routesByApp.computeIfAbsent(parts[0], k -> new LinkedHashSet<>()).add(parts[1]);
            }
        }

        try {
            List<RouteCatalogEntry> catalogEntries = routeCatalogStore.findByEnvironment(envSlug, rangeFrom, rangeTo);
            for (RouteCatalogEntry entry : catalogEntries) {
                routesByApp.computeIfAbsent(entry.applicationId(), k -> new LinkedHashSet<>())
                           .add(entry.routeId());
            }
        } catch (Exception e) {
            log.warn("Failed to query route catalog: {}", e.getMessage());
        }

        Set<String> allAppIds = new LinkedHashSet<>(agentsByApp.keySet());
        allAppIds.addAll(routesByApp.keySet());

        List<AppCatalogEntry> catalog = new ArrayList<>();
        for (String appId : allAppIds) {
            List<AgentInfo> agents = agentsByApp.getOrDefault(appId, List.of());

            Set<String> routeIds = routesByApp.getOrDefault(appId, Set.of());
            List<RouteSummary> routeSummaries = routeIds.stream()
                    .map(routeId -> {
                        String key = appId + "/" + routeId;
                        long count = routeExchangeCounts.getOrDefault(key, 0L);
                        Instant lastSeen = routeLastSeen.get(key);
                        String fromUri = resolveFromEndpointUri(appId, routeId, envSlug);
                        String state = routeStateRegistry.getState(appId, routeId).name().toLowerCase();
                        String routeState = "started".equals(state) ? null : state;
                        return new RouteSummary(routeId, count, lastSeen, fromUri, routeState);
                    })
                    .toList();

            List<AgentSummary> agentSummaries = agents.stream()
                    .map(a -> new AgentSummary(a.instanceId(), a.displayName(), a.state().name().toLowerCase(), 0.0))
                    .toList();

            String health = computeWorstHealth(agents);

            long totalExchanges = routeSummaries.stream().mapToLong(RouteSummary::exchangeCount).sum();

            catalog.add(new AppCatalogEntry(appId, routeSummaries, agentSummaries,
                    agents.size(), health, totalExchanges));
        }

        return ResponseEntity.ok(catalog);
    }

    private String resolveFromEndpointUri(String applicationId, String routeId, String environment) {
        return diagramStore.findLatestContentHashForAppRoute(applicationId, routeId, environment)
                .flatMap(diagramStore::findByContentHash)
                .map(RouteGraph::getRoot)
                .map(root -> root.getEndpointUri())
                .orElse(null);
    }

    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)) + "'";
    }

    private static String lit(String value) {
        return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
    }

    private String computeWorstHealth(List<AgentInfo> agents) {
        boolean hasDead = false;
        boolean hasStale = false;
        for (AgentInfo a : agents) {
            if (a.state() == AgentState.DEAD) hasDead = true;
            if (a.state() == AgentState.STALE) hasStale = true;
        }
        if (hasDead) return "dead";
        if (hasStale) return "stale";
        return "live";
    }
}
