package com.cameleer.server.app.metrics;

import com.cameleer.server.app.agent.SseConnectionManager;
import com.cameleer.server.core.agent.AgentRegistryService;
import com.cameleer.server.core.agent.AgentState;
import com.cameleer.server.core.ingestion.BufferedLogEntry;
import com.cameleer.server.core.ingestion.ChunkAccumulator;
import com.cameleer.server.core.ingestion.MergedExecution;
import com.cameleer.server.core.ingestion.WriteBuffer;
import com.cameleer.server.core.storage.model.MetricsSnapshot;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Component
public class ServerMetrics {

    private final MeterRegistry registry;

    // Counters
    private final Counter ingestionDropsBufferFull;
    private final Counter ingestionDropsNoAgent;
    private final Counter ingestionDropsNoIdentity;
    private final Counter transitionsWentStale;
    private final Counter transitionsWentDead;
    private final Counter transitionsRecovered;
    private final Counter deploymentsRunning;
    private final Counter deploymentsFailed;
    private final Counter deploymentsDegraded;
    private final Counter authFailuresInvalidToken;
    private final Counter authFailuresRevoked;
    private final Counter authFailuresOidc;

    // Timers
    private final Timer flushExecutionTimer;
    private final Timer flushProcessorTimer;
    private final Timer flushLogTimer;
    private final Timer deploymentDurationTimer;

    public ServerMetrics(MeterRegistry registry,
                         AgentRegistryService registryService,
                         SseConnectionManager sseManager,
                         WriteBuffer<MergedExecution> executionBuffer,
                         WriteBuffer<ChunkAccumulator.ProcessorBatch> processorBuffer,
                         WriteBuffer<BufferedLogEntry> logBuffer,
                         WriteBuffer<MetricsSnapshot> metricsBuffer,
                         ChunkAccumulator accumulator) {
        this.registry = registry;

        // ── Gauges (auto-polled) ────────────────────────────────────────

        for (AgentState state : AgentState.values()) {
            Gauge.builder("cameleer.agents.connected", registryService,
                            r -> r.findByState(state).size())
                    .tag("state", state.name().toLowerCase())
                    .register(registry);
        }

        Gauge.builder("cameleer.agents.sse.active", sseManager,
                        SseConnectionManager::getConnectionCount)
                .register(registry);

        Gauge.builder("cameleer.ingestion.buffer.size", executionBuffer, WriteBuffer::size)
                .tag("type", "execution")
                .register(registry);
        Gauge.builder("cameleer.ingestion.buffer.size", processorBuffer, WriteBuffer::size)
                .tag("type", "processor")
                .register(registry);
        Gauge.builder("cameleer.ingestion.buffer.size", logBuffer, WriteBuffer::size)
                .tag("type", "log")
                .register(registry);
        Gauge.builder("cameleer.ingestion.buffer.size", metricsBuffer, WriteBuffer::size)
                .tag("type", "metrics")
                .register(registry);

        Gauge.builder("cameleer.ingestion.accumulator.pending", accumulator,
                        ChunkAccumulator::getPendingCount)
                .register(registry);

        // ── Counters ────────────────────────────────────────────────────

        ingestionDropsBufferFull = Counter.builder("cameleer.ingestion.drops")
                .tag("reason", "buffer_full").register(registry);
        ingestionDropsNoAgent = Counter.builder("cameleer.ingestion.drops")
                .tag("reason", "no_agent").register(registry);
        ingestionDropsNoIdentity = Counter.builder("cameleer.ingestion.drops")
                .tag("reason", "no_identity").register(registry);

        transitionsWentStale = Counter.builder("cameleer.agents.transitions")
                .tag("transition", "went_stale").register(registry);
        transitionsWentDead = Counter.builder("cameleer.agents.transitions")
                .tag("transition", "went_dead").register(registry);
        transitionsRecovered = Counter.builder("cameleer.agents.transitions")
                .tag("transition", "recovered").register(registry);

        deploymentsRunning = Counter.builder("cameleer.deployments.outcome")
                .tag("status", "running").register(registry);
        deploymentsFailed = Counter.builder("cameleer.deployments.outcome")
                .tag("status", "failed").register(registry);
        deploymentsDegraded = Counter.builder("cameleer.deployments.outcome")
                .tag("status", "degraded").register(registry);

        authFailuresInvalidToken = Counter.builder("cameleer.auth.failures")
                .tag("reason", "invalid_token").register(registry);
        authFailuresRevoked = Counter.builder("cameleer.auth.failures")
                .tag("reason", "revoked").register(registry);
        authFailuresOidc = Counter.builder("cameleer.auth.failures")
                .tag("reason", "oidc_rejected").register(registry);

        // ── Timers ──────────────────────────────────────────────────────

        flushExecutionTimer = Timer.builder("cameleer.ingestion.flush.duration")
                .tag("type", "execution").register(registry);
        flushProcessorTimer = Timer.builder("cameleer.ingestion.flush.duration")
                .tag("type", "processor").register(registry);
        flushLogTimer = Timer.builder("cameleer.ingestion.flush.duration")
                .tag("type", "log").register(registry);

        deploymentDurationTimer = Timer.builder("cameleer.deployments.duration")
                .register(registry);
    }

    // ── Ingestion ───────────────────────────────────────────────────────

    public void recordIngestionDrop(String reason) {
        switch (reason) {
            case "buffer_full" -> ingestionDropsBufferFull.increment();
            case "no_agent" -> ingestionDropsNoAgent.increment();
            case "no_identity" -> ingestionDropsNoIdentity.increment();
        }
    }

    public void recordIngestionDrops(String reason, int count) {
        switch (reason) {
            case "buffer_full" -> ingestionDropsBufferFull.increment(count);
            case "no_agent" -> ingestionDropsNoAgent.increment(count);
            case "no_identity" -> ingestionDropsNoIdentity.increment(count);
        }
    }

    // ── Agent lifecycle ─────────────────────────────────────────────────

    public void recordAgentTransition(String transition) {
        switch (transition) {
            case "WENT_STALE" -> transitionsWentStale.increment();
            case "WENT_DEAD" -> transitionsWentDead.increment();
            case "RECOVERED" -> transitionsRecovered.increment();
        }
    }

    // ── Deployments ─────────────────────────────────────────────────────

    public void recordDeploymentOutcome(String status) {
        switch (status) {
            case "RUNNING" -> deploymentsRunning.increment();
            case "FAILED" -> deploymentsFailed.increment();
            case "DEGRADED" -> deploymentsDegraded.increment();
        }
    }

    public void recordDeploymentDuration(long startMillis) {
        deploymentDurationTimer.record(
                System.currentTimeMillis() - startMillis, TimeUnit.MILLISECONDS);
    }

    // ── Flush timers ────────────────────────────────────────────────────

    public void recordFlushDuration(String type, Duration duration) {
        switch (type) {
            case "execution" -> flushExecutionTimer.record(duration);
            case "processor" -> flushProcessorTimer.record(duration);
            case "log" -> flushLogTimer.record(duration);
        }
    }

    // ── Auth ────────────────────────────────────────────────────────────

    public void recordAuthFailure(String reason) {
        switch (reason) {
            case "invalid_token" -> authFailuresInvalidToken.increment();
            case "revoked" -> authFailuresRevoked.increment();
            case "oidc_rejected" -> authFailuresOidc.increment();
        }
    }
}
