package com.cameleer.server.app.controller;

import com.cameleer.server.app.web.EnvPath;
import com.cameleer.server.core.admin.AppSettings;
import com.cameleer.server.core.admin.AppSettingsRepository;
import com.cameleer.server.core.runtime.Environment;
import com.cameleer.server.core.search.AttributeFilter;
import com.cameleer.server.core.search.ExecutionStats;
import com.cameleer.server.core.search.ExecutionSummary;
import com.cameleer.server.core.search.SearchRequest;
import com.cameleer.server.core.search.SearchResult;
import com.cameleer.server.core.search.SearchService;
import com.cameleer.server.core.search.StatsTimeseries;
import com.cameleer.server.core.search.TopError;
import com.cameleer.server.core.storage.StatsStore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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 org.springframework.web.server.ResponseStatusException;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Execution search and stats endpoints. Env is the path; env filter is
 * derived from the path and always applied to underlying ClickHouse queries.
 */
@RestController
@RequestMapping("/api/v1/environments/{envSlug}")
@Tag(name = "Search", description = "Transaction search and stats (env-scoped)")
public class SearchController {

    private final SearchService searchService;
    private final AppSettingsRepository appSettingsRepository;

    public SearchController(SearchService searchService,
                             AppSettingsRepository appSettingsRepository) {
        this.searchService = searchService;
        this.appSettingsRepository = appSettingsRepository;
    }

    @GetMapping("/executions")
    @Operation(summary = "Search executions with basic filters (env from path)")
    public ResponseEntity<SearchResult<ExecutionSummary>> searchGet(
            @EnvPath Environment env,
            @RequestParam(required = false) String status,
            @RequestParam(required = false) Instant timeFrom,
            @RequestParam(required = false) Instant timeTo,
            @RequestParam(required = false) String correlationId,
            @RequestParam(required = false) String text,
            @RequestParam(required = false) String routeId,
            @RequestParam(name = "agentId", required = false) String instanceId,
            @RequestParam(required = false) String processorType,
            @RequestParam(required = false) String application,
            @RequestParam(name = "attr", required = false) List<String> attr,
            @RequestParam(defaultValue = "0") int offset,
            @RequestParam(defaultValue = "50") int limit,
            @RequestParam(required = false) String sortField,
            @RequestParam(required = false) String sortDir) {

        List<AttributeFilter> attributeFilters;
        try {
            attributeFilters = parseAttrParams(attr);
        } catch (IllegalArgumentException e) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e);
        }

        SearchRequest request = new SearchRequest(
                status, timeFrom, timeTo,
                null, null,
                correlationId,
                text, null, null, null,
                routeId, instanceId, processorType,
                application, null,
                offset, limit,
                sortField, sortDir,
                null,
                env.slug(),
                attributeFilters
        );

        return ResponseEntity.ok(searchService.search(request));
    }

    /**
     * Parses {@code attr} query params of the form {@code key} (key-only) or {@code key:value}
     * (exact or wildcard via {@code *}). Splits on the first {@code :}; later colons are part of
     * the value. Blank / null list → empty result. Key validation is delegated to
     * {@link AttributeFilter}'s compact constructor, which throws {@link IllegalArgumentException}
     * on invalid keys (mapped to 400 by the caller).
     */
    static List<AttributeFilter> parseAttrParams(List<String> raw) {
        if (raw == null || raw.isEmpty()) return List.of();
        List<AttributeFilter> out = new ArrayList<>(raw.size());
        for (String entry : raw) {
            if (entry == null || entry.isBlank()) continue;
            int colon = entry.indexOf(':');
            if (colon < 0) {
                out.add(new AttributeFilter(entry.trim(), null));
            } else {
                out.add(new AttributeFilter(entry.substring(0, colon).trim(),
                        entry.substring(colon + 1)));
            }
        }
        return out;
    }

    @PostMapping("/executions/search")
    @Operation(summary = "Advanced search with all filters",
            description = "Env from the path overrides any environment field in the body.")
    public ResponseEntity<SearchResult<ExecutionSummary>> searchPost(
            @EnvPath Environment env,
            @RequestBody SearchRequest request) {
        SearchRequest scoped = request.withEnvironment(env.slug());
        return ResponseEntity.ok(searchService.search(scoped));
    }

    @GetMapping("/stats")
    @Operation(summary = "Aggregate execution stats (P99 latency, active count, SLA compliance)")
    public ResponseEntity<ExecutionStats> stats(
            @EnvPath Environment env,
            @RequestParam Instant from,
            @RequestParam(required = false) Instant to,
            @RequestParam(required = false) String routeId,
            @RequestParam(required = false) String application) {
        Instant end = to != null ? to : Instant.now();
        ExecutionStats stats;
        if (routeId == null && application == null) {
            stats = searchService.stats(from, end, env.slug());
        } else if (routeId == null) {
            stats = searchService.statsForApp(from, end, application, env.slug());
        } else {
            stats = searchService.statsForRoute(from, end, routeId, application, env.slug());
        }

        int threshold = application != null && !application.isBlank()
                ? appSettingsRepository.findByApplicationAndEnvironment(application, env.slug())
                        .map(AppSettings::slaThresholdMs).orElse(300)
                : 300;
        double sla = searchService.slaCompliance(from, end, threshold, application, routeId, env.slug());
        return ResponseEntity.ok(stats.withSlaCompliance(sla));
    }

    @GetMapping("/stats/timeseries")
    @Operation(summary = "Bucketed time-series stats over a time window")
    public ResponseEntity<StatsTimeseries> timeseries(
            @EnvPath Environment env,
            @RequestParam Instant from,
            @RequestParam(required = false) Instant to,
            @RequestParam(defaultValue = "24") int buckets,
            @RequestParam(required = false) String routeId,
            @RequestParam(required = false) String application) {
        Instant end = to != null ? to : Instant.now();
        if (routeId == null && application == null) {
            return ResponseEntity.ok(searchService.timeseries(from, end, buckets, env.slug()));
        }
        if (routeId == null) {
            return ResponseEntity.ok(searchService.timeseriesForApp(from, end, buckets, application, env.slug()));
        }
        return ResponseEntity.ok(searchService.timeseriesForRoute(from, end, buckets, routeId, application, env.slug()));
    }

    @GetMapping("/stats/timeseries/by-app")
    @Operation(summary = "Timeseries grouped by application")
    public ResponseEntity<Map<String, StatsTimeseries>> timeseriesByApp(
            @EnvPath Environment env,
            @RequestParam Instant from,
            @RequestParam(required = false) Instant to,
            @RequestParam(defaultValue = "24") int buckets) {
        Instant end = to != null ? to : Instant.now();
        return ResponseEntity.ok(searchService.timeseriesGroupedByApp(from, end, buckets, env.slug()));
    }

    @GetMapping("/stats/timeseries/by-route")
    @Operation(summary = "Timeseries grouped by route for an application")
    public ResponseEntity<Map<String, StatsTimeseries>> timeseriesByRoute(
            @EnvPath Environment env,
            @RequestParam Instant from,
            @RequestParam(required = false) Instant to,
            @RequestParam(defaultValue = "24") int buckets,
            @RequestParam String application) {
        Instant end = to != null ? to : Instant.now();
        return ResponseEntity.ok(searchService.timeseriesGroupedByRoute(from, end, buckets, application, env.slug()));
    }

    @GetMapping("/stats/punchcard")
    @Operation(summary = "Transaction punchcard: weekday x hour grid (rolling 7 days)")
    public ResponseEntity<List<StatsStore.PunchcardCell>> punchcard(
            @EnvPath Environment env,
            @RequestParam(required = false) String application) {
        Instant to = Instant.now();
        Instant from = to.minus(java.time.Duration.ofDays(7));
        return ResponseEntity.ok(searchService.punchcard(from, to, application, env.slug()));
    }

    @GetMapping("/attributes/keys")
    @Operation(summary = "Distinct attribute key names for this environment")
    public ResponseEntity<List<String>> attributeKeys(@EnvPath Environment env) {
        return ResponseEntity.ok(searchService.distinctAttributeKeys(env.slug()));
    }

    @GetMapping("/errors/top")
    @Operation(summary = "Top N errors with velocity trend")
    public ResponseEntity<List<TopError>> topErrors(
            @EnvPath Environment env,
            @RequestParam Instant from,
            @RequestParam(required = false) Instant to,
            @RequestParam(required = false) String application,
            @RequestParam(required = false) String routeId,
            @RequestParam(defaultValue = "5") int limit) {
        Instant end = to != null ? to : Instant.now();
        return ResponseEntity.ok(searchService.topErrors(from, end, application, routeId, limit, env.slug()));
    }
}
