package com.cameleer.server.app.alerting.eval;

import com.cameleer.server.core.agent.AgentEventRecord;
import com.cameleer.server.core.agent.AgentEventRepository;
import com.cameleer.server.core.alerting.AgentLifecycleCondition;
import com.cameleer.server.core.alerting.AgentLifecycleEventType;
import com.cameleer.server.core.alerting.AlertRule;
import com.cameleer.server.core.alerting.AlertScope;
import com.cameleer.server.core.alerting.ConditionKind;
import com.cameleer.server.core.runtime.EnvironmentRepository;
import org.springframework.stereotype.Component;

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

/**
 * Evaluator for {@link AgentLifecycleCondition}.
 * <p>
 * Each matching row in {@code agent_events} produces its own {@link EvalResult.Firing}
 * in an {@link EvalResult.Batch}, so every {@code (agent, eventType, timestamp)}
 * tuple gets its own {@code AlertInstance} — operationally distinct outages /
 * restarts / shutdowns are independently ackable. Deduplication across ticks
 * is enforced by {@code alert_instances_open_rule_uq} via the canonical
 * {@code _subjectFingerprint} key in the instance context (see V16 migration).
 */
@Component
public class AgentLifecycleEvaluator implements ConditionEvaluator<AgentLifecycleCondition> {

    /** Hard cap on rows returned per tick — prevents a flood of stale events from overwhelming the job. */
    private static final int MAX_EVENTS_PER_TICK = 500;

    private final AgentEventRepository eventRepo;
    private final EnvironmentRepository envRepo;

    public AgentLifecycleEvaluator(AgentEventRepository eventRepo, EnvironmentRepository envRepo) {
        this.eventRepo = eventRepo;
        this.envRepo   = envRepo;
    }

    @Override
    public ConditionKind kind() { return ConditionKind.AGENT_LIFECYCLE; }

    @Override
    public EvalResult evaluate(AgentLifecycleCondition c, AlertRule rule, EvalContext ctx) {
        String envSlug = envRepo.findById(rule.environmentId())
                .map(e -> e.slug())
                .orElse(null);
        if (envSlug == null) return EvalResult.Clear.INSTANCE;

        AlertScope scope = c.scope();
        String appSlug  = scope != null ? scope.appSlug()  : null;
        String agentId  = scope != null ? scope.agentId()  : null;

        List<String> typeNames = c.eventTypes().stream()
                .map(AgentLifecycleEventType::name)
                .toList();

        Instant from = ctx.now().minusSeconds(c.withinSeconds());
        Instant to   = ctx.now();

        List<AgentEventRecord> matches = eventRepo.findInWindow(
                envSlug, appSlug, agentId, typeNames, from, to, MAX_EVENTS_PER_TICK);

        if (matches.isEmpty()) return new EvalResult.Batch(List.of(), Map.of());

        List<EvalResult.Firing> firings = new ArrayList<>(matches.size());
        for (AgentEventRecord ev : matches) {
            firings.add(toFiring(ev));
        }
        return new EvalResult.Batch(firings, Map.of());
    }

    private static EvalResult.Firing toFiring(AgentEventRecord ev) {
        String fingerprint = (ev.instanceId() == null ? "" : ev.instanceId())
                + ":" + (ev.eventType() == null ? "" : ev.eventType())
                + ":" + (ev.timestamp() == null ? "0" : Long.toString(ev.timestamp().toEpochMilli()));

        Map<String, Object> context = new LinkedHashMap<>();
        context.put("agent", Map.of(
                "id",  ev.instanceId()    == null ? "" : ev.instanceId(),
                "app", ev.applicationId() == null ? "" : ev.applicationId()
        ));
        context.put("event", Map.of(
                "type",      ev.eventType() == null ? "" : ev.eventType(),
                "timestamp", ev.timestamp() == null ? "" : ev.timestamp().toString(),
                "detail",    ev.detail()    == null ? "" : ev.detail()
        ));
        context.put("_subjectFingerprint", fingerprint);

        return new EvalResult.Firing(1.0, null, context);
    }
}
