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

import com.cameleer.server.core.alerting.AlertInstance;
import com.cameleer.server.core.alerting.AlertRule;
import com.cameleer.server.core.alerting.AlertRuleTarget;
import com.cameleer.server.core.alerting.AlertSeverity;
import com.cameleer.server.core.alerting.AlertState;
import com.cameleer.server.core.alerting.TargetKind;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * Pure, stateless state-machine for alert instance transitions.
 * <p>
 * Given the current open instance (nullable) and an EvalResult, returns the new/updated
 * AlertInstance or {@link Optional#empty()} when no action is needed.
 * <p>
 * Batch results must be handled directly in the job; this helper returns empty for them.
 */
public final class AlertStateTransitions {

    private AlertStateTransitions() {}

    /**
     * Apply an EvalResult to the current open AlertInstance.
     *
     * @param current the open instance for this rule (PENDING / FIRING), or null if none
     * @param result  the evaluator outcome
     * @param rule    the rule being evaluated
     * @param now     wall-clock instant for the current tick
     * @return the new or updated AlertInstance, or empty when nothing should change
     */
    public static Optional<AlertInstance> apply(
            AlertInstance current, EvalResult result, AlertRule rule, Instant now) {

        if (result instanceof EvalResult.Clear)            return onClear(current, now);
        if (result instanceof EvalResult.Firing f)          return onFiring(current, f, rule, now);
        // EvalResult.Error and EvalResult.Batch — no action (Batch handled by the job directly)
        return Optional.empty();
    }

    // -------------------------------------------------------------------------
    // Clear branch
    // -------------------------------------------------------------------------

    private static Optional<AlertInstance> onClear(AlertInstance current, Instant now) {
        if (current == null) return Optional.empty();                   // no open instance — no-op
        if (current.state() == AlertState.RESOLVED) return Optional.empty(); // already resolved
        // Any open state (PENDING / FIRING) → RESOLVED
        return Optional.of(current
                .withState(AlertState.RESOLVED)
                .withResolvedAt(now));
    }

    // -------------------------------------------------------------------------
    // Firing branch
    // -------------------------------------------------------------------------

    private static Optional<AlertInstance> onFiring(
            AlertInstance current, EvalResult.Firing f, AlertRule rule, Instant now) {

        if (current == null) {
            // No open instance — create a new one
            AlertState initial = rule.forDurationSeconds() > 0
                    ? AlertState.PENDING
                    : AlertState.FIRING;
            return Optional.of(newInstance(rule, f, initial, now));
        }

        return switch (current.state()) {
            case PENDING -> {
                // Check whether the forDuration window has elapsed
                Instant promoteAt = current.firedAt().plusSeconds(rule.forDurationSeconds());
                if (!promoteAt.isAfter(now)) {
                    // Promote to FIRING; keep the original firedAt (that's when it first appeared)
                    yield Optional.of(current
                            .withState(AlertState.FIRING)
                            .withFiredAt(now));
                }
                // Still within forDuration — stay PENDING, nothing to persist
                yield Optional.empty();
            }
            // FIRING — re-notification cadence handled by the dispatcher
            case FIRING -> Optional.empty();
            // RESOLVED should never appear as the "current open" instance, but guard anyway
            case RESOLVED -> Optional.empty();
        };
    }

    // -------------------------------------------------------------------------
    // Factory helpers
    // -------------------------------------------------------------------------

    /**
     * Creates a brand-new AlertInstance from a rule + Firing result.
     * title/message are left empty here; the job enriches them via MustacheRenderer after.
     */
    static AlertInstance newInstance(AlertRule rule, EvalResult.Firing f, AlertState state, Instant now) {
        List<AlertRuleTarget> targets = rule.targets() != null ? rule.targets() : List.of();
        List<String> targetUserIds = targets.stream()
                .filter(t -> t.kind() == TargetKind.USER)
                .map(AlertRuleTarget::targetId)
                .toList();
        List<UUID> targetGroupIds = targets.stream()
                .filter(t -> t.kind() == TargetKind.GROUP)
                .map(t -> UUID.fromString(t.targetId()))
                .toList();
        List<String> targetRoleNames = targets.stream()
                .filter(t -> t.kind() == TargetKind.ROLE)
                .map(AlertRuleTarget::targetId)
                .toList();

        return new AlertInstance(
                UUID.randomUUID(),
                rule.id(),
                Map.of(),                          // ruleSnapshot — caller (job) fills in via ObjectMapper
                rule.environmentId(),
                state,
                rule.severity() != null ? rule.severity() : AlertSeverity.WARNING,
                now,                               // firedAt
                null,                              // ackedAt
                null,                              // ackedBy
                null,                              // resolvedAt
                null,                              // lastNotifiedAt
                null,                              // readAt
                null,                              // deletedAt
                false,                             // silenced
                f.currentValue(),
                f.threshold(),
                f.context() != null ? f.context() : Map.of(),
                "",                                // title — rendered by job
                "",                                // message — rendered by job
                targetUserIds,
                targetGroupIds,
                targetRoleNames);
    }
}
