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

import com.cameleer.server.core.alerting.AlertInstance;
import com.cameleer.server.core.alerting.AlertRule;
import com.cameleer.server.core.runtime.Environment;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Builds the Mustache template context map from an AlertRule + AlertInstance + Environment.
 * <p>
 * Always present: {@code env}, {@code rule}, {@code alert}.
 * Conditionally present based on {@code rule.conditionKind()}:
 * <ul>
 *   <li>AGENT_STATE      → {@code agent}, {@code app}</li>
 *   <li>DEPLOYMENT_STATE → {@code deployment}, {@code app}</li>
 *   <li>ROUTE_METRIC     → {@code route}, {@code app}</li>
 *   <li>EXCHANGE_MATCH   → {@code exchange}, {@code app}, {@code route}</li>
 *   <li>LOG_PATTERN      → {@code log}, {@code app}</li>
 *   <li>JVM_METRIC       → {@code metric}, {@code agent}, {@code app}</li>
 * </ul>
 * Values absent from {@code instance.context()} render as empty string so Mustache templates
 * remain valid even for env-wide rules that have no app/route scope.
 */
@Component
public class NotificationContextBuilder {

    public Map<String, Object> build(AlertRule rule, AlertInstance instance, Environment env, String uiOrigin) {
        Map<String, Object> ctx = new LinkedHashMap<>();

        // --- env subtree ---
        ctx.put("env", Map.of(
                "slug", env.slug(),
                "id",   env.id().toString()
        ));

        // --- rule subtree ---
        ctx.put("rule", Map.of(
                "id",          rule.id().toString(),
                "name",        rule.name(),
                "severity",    rule.severity().name(),
                "description", rule.description() == null ? "" : rule.description()
        ));

        // --- alert subtree ---
        String base = uiOrigin == null ? "" : uiOrigin;
        ctx.put("alert", Map.of(
                "id",           instance.id().toString(),
                "state",        instance.state().name(),
                "firedAt",      instance.firedAt().toString(),
                "resolvedAt",   instance.resolvedAt()    == null ? "" : instance.resolvedAt().toString(),
                "ackedBy",      instance.ackedBy()       == null ? "" : instance.ackedBy(),
                "link",         base + "/alerts/inbox/" + instance.id(),
                "currentValue", instance.currentValue()  == null ? "" : instance.currentValue().toString(),
                "threshold",    instance.threshold()     == null ? "" : instance.threshold().toString()
        ));

        // --- per-kind conditional subtrees ---
        if (rule.conditionKind() != null) {
            switch (rule.conditionKind()) {
                case AGENT_STATE -> {
                    ctx.put("agent", subtree(instance, "agent.id", "agent.name", "agent.state"));
                    ctx.put("app",   subtree(instance, "app.slug", "app.id"));
                }
                case AGENT_LIFECYCLE -> {
                    ctx.put("agent", subtree(instance, "agent.id", "agent.app"));
                    ctx.put("event", subtree(instance, "event.type", "event.timestamp", "event.detail"));
                }
                case DEPLOYMENT_STATE -> {
                    ctx.put("deployment", subtree(instance, "deployment.id", "deployment.status"));
                    ctx.put("app",        subtree(instance, "app.slug", "app.id"));
                }
                case ROUTE_METRIC -> {
                    ctx.put("route", subtree(instance, "route.id", "route.uri"));
                    ctx.put("app",   subtree(instance, "app.slug", "app.id"));
                }
                case EXCHANGE_MATCH -> {
                    ctx.put("exchange", subtree(instance, "exchange.id", "exchange.status"));
                    ctx.put("app",      subtree(instance, "app.slug", "app.id"));
                    ctx.put("route",    subtree(instance, "route.id", "route.uri"));
                }
                case LOG_PATTERN -> {
                    ctx.put("log", subtree(instance, "log.pattern", "log.matchCount"));
                    ctx.put("app", subtree(instance, "app.slug", "app.id"));
                }
                case JVM_METRIC -> {
                    ctx.put("metric", subtree(instance, "metric.name", "metric.value"));
                    ctx.put("agent",  subtree(instance, "agent.id", "agent.name"));
                    ctx.put("app",    subtree(instance, "app.slug", "app.id"));
                }
            }
        }

        return ctx;
    }

    /**
     * Extracts a flat subtree from {@code instance.context()} using dotted key paths.
     * Each path like {@code "agent.id"} becomes the leaf key {@code "id"} in the returned map.
     * Missing or null values are stored as empty string.
     */
    private Map<String, Object> subtree(AlertInstance instance, String... dottedPaths) {
        Map<String, Object> sub = new LinkedHashMap<>();
        Map<String, Object> ic = instance.context();
        for (String path : dottedPaths) {
            String leafKey = path.contains(".") ? path.substring(path.lastIndexOf('.') + 1) : path;
            Object val = resolveContext(ic, path);
            sub.put(leafKey, val == null ? "" : val.toString());
        }
        return sub;
    }

    @SuppressWarnings("unchecked")
    private Object resolveContext(Map<String, Object> ctx, String path) {
        if (ctx == null) return null;
        String[] parts = path.split("\\.");
        Object current = ctx.get(parts[0]);
        for (int i = 1; i < parts.length; i++) {
            if (!(current instanceof Map)) return null;
            current = ((Map<String, Object>) current).get(parts[i]);
        }
        return current;
    }
}
