package com.cameleer.server.app.controller;

import com.cameleer.server.core.storage.ServerMetricsQueryStore;
import com.cameleer.server.core.storage.model.ServerInstanceInfo;
import com.cameleer.server.core.storage.model.ServerMetricCatalogEntry;
import com.cameleer.server.core.storage.model.ServerMetricQueryRequest;
import com.cameleer.server.core.storage.model.ServerMetricQueryResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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.util.List;
import java.util.Map;

/**
 * Generic read API over the ClickHouse {@code server_metrics} table. Lets
 * SaaS control planes build server-health dashboards without requiring direct
 * ClickHouse access.
 *
 * <p>Three endpoints cover all 17 panels in {@code docs/server-self-metrics.md}:
 * <ul>
 *   <li>{@code GET /catalog} — discover available metric names, types, statistics, and tags</li>
 *   <li>{@code POST /query} — generic time-series query with aggregation, grouping, filtering, and counter-delta mode</li>
 *   <li>{@code GET /instances} — list server instances (useful for partitioning counter math)</li>
 * </ul>
 *
 * <p>Visibility matches {@code ClickHouseAdminController} / {@code DatabaseAdminController}:
 * <ul>
 *   <li>Conditional on {@code cameleer.server.security.infrastructureendpoints=true} (default).</li>
 *   <li>Class-level {@code @PreAuthorize("hasRole('ADMIN')")} on top of the
 *       {@code /api/v1/admin/**} catch-all in {@code SecurityConfig}.</li>
 * </ul>
 */
@ConditionalOnProperty(
    name = "cameleer.server.security.infrastructureendpoints",
    havingValue = "true",
    matchIfMissing = true
)
@RestController
@RequestMapping("/api/v1/admin/server-metrics")
@PreAuthorize("hasRole('ADMIN')")
@Tag(name = "Server Self-Metrics",
     description = "Read API over the server's own Micrometer registry snapshots (ADMIN only)")
public class ServerMetricsAdminController {

    /** Default lookback window for catalog/instances when from/to are omitted. */
    private static final long DEFAULT_LOOKBACK_SECONDS = 3_600L;

    private final ServerMetricsQueryStore store;

    public ServerMetricsAdminController(ServerMetricsQueryStore store) {
        this.store = store;
    }

    @GetMapping("/catalog")
    @Operation(summary = "List metric names observed in the window",
               description = "For each metric_name, returns metric_type, the set of statistics emitted, and the union of tag keys.")
    public ResponseEntity<List<ServerMetricCatalogEntry>> catalog(
            @RequestParam(required = false) String from,
            @RequestParam(required = false) String to) {
        Instant[] window = resolveWindow(from, to);
        return ResponseEntity.ok(store.catalog(window[0], window[1]));
    }

    @GetMapping("/instances")
    @Operation(summary = "List server_instance_id values observed in the window",
               description = "Returns first/last seen timestamps — use to partition counter-delta computations.")
    public ResponseEntity<List<ServerInstanceInfo>> instances(
            @RequestParam(required = false) String from,
            @RequestParam(required = false) String to) {
        Instant[] window = resolveWindow(from, to);
        return ResponseEntity.ok(store.listInstances(window[0], window[1]));
    }

    @PostMapping("/query")
    @Operation(summary = "Generic time-series query",
               description = "Returns bucketed series for a single metric_name. Supports aggregation (avg/sum/max/min/latest), group-by-tag, filter-by-tag, counter delta mode, and a derived 'mean' statistic for timers.")
    public ResponseEntity<ServerMetricQueryResponse> query(@RequestBody QueryBody body) {
        ServerMetricQueryRequest request = new ServerMetricQueryRequest(
                body.metric(),
                body.statistic(),
                parseInstant(body.from(), "from"),
                parseInstant(body.to(), "to"),
                body.stepSeconds(),
                body.groupByTags(),
                body.filterTags(),
                body.aggregation(),
                body.mode(),
                body.serverInstanceIds());
        return ResponseEntity.ok(store.query(request));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, String>> handleBadRequest(IllegalArgumentException e) {
        return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
    }

    private static Instant[] resolveWindow(String from, String to) {
        Instant toI = to != null ? parseInstant(to, "to") : Instant.now();
        Instant fromI = from != null
                ? parseInstant(from, "from")
                : toI.minusSeconds(DEFAULT_LOOKBACK_SECONDS);
        if (!fromI.isBefore(toI)) {
            throw new IllegalArgumentException("from must be strictly before to");
        }
        return new Instant[]{fromI, toI};
    }

    private static Instant parseInstant(String raw, String field) {
        if (raw == null || raw.isBlank()) {
            throw new IllegalArgumentException(field + " is required");
        }
        try {
            return Instant.parse(raw);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    field + " must be an ISO-8601 instant (e.g. 2026-04-23T10:00:00Z)");
        }
    }

    /**
     * Request body for {@link #query(QueryBody)}. Uses ISO-8601 strings on
     * the wire so the OpenAPI schema stays language-neutral.
     */
    public record QueryBody(
            String metric,
            String statistic,
            String from,
            String to,
            Integer stepSeconds,
            List<String> groupByTags,
            Map<String, String> filterTags,
            String aggregation,
            String mode,
            List<String> serverInstanceIds
    ) {
    }
}
