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

import com.samskivert.mustache.Mustache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Renders Mustache templates against a context map.
 * <p>
 * Contract:
 * <ul>
 *   <li>Unresolved {@code {{x.y.z}}} tokens render as the literal {@code {{x.y.z}}} and log WARN.</li>
 *   <li>Malformed templates (e.g. unclosed {@code {{}) return the original template string and log WARN.</li>
 *   <li>Never throws on template content.</li>
 * </ul>
 */
@Component
public class MustacheRenderer {

    private static final Logger log = LoggerFactory.getLogger(MustacheRenderer.class);

    /** Matches {{path}} tokens, capturing the trimmed path. Ignores triple-mustache and comments. */
    private static final Pattern TOKEN = Pattern.compile("\\{\\{\\s*([^#/!>{\\s][^}]*)\\s*\\}\\}");

    /** Sentinel prefix/suffix to survive Mustache compilation so we can post-replace. */
    private static final String SENTINEL_PREFIX = "\u0000TPL\u0001";
    private static final String SENTINEL_SUFFIX = "\u0001LPT\u0000";

    public String render(String template, Map<String, Object> ctx) {
        if (template == null) return "";
        try {
            // 1) Walk all {{path}} tokens. Those unresolved get replaced with a unique sentinel.
            Map<String, String> literals = new LinkedHashMap<>();
            StringBuilder pre = new StringBuilder();
            Matcher m = TOKEN.matcher(template);
            int sentinelIdx = 0;
            boolean anyUnresolved = false;
            while (m.find()) {
                String path = m.group(1).trim();
                if (resolvePath(ctx, path) == null) {
                    anyUnresolved = true;
                    String sentinelKey = SENTINEL_PREFIX + sentinelIdx++ + SENTINEL_SUFFIX;
                    literals.put(sentinelKey, "{{" + path + "}}");
                    m.appendReplacement(pre, Matcher.quoteReplacement(sentinelKey));
                }
            }
            m.appendTail(pre);
            if (anyUnresolved) {
                log.warn("MustacheRenderer: unresolved template variables; rendering as literals. template={}",
                        template.length() > 200 ? template.substring(0, 200) + "..." : template);
            }

            // 2) Compile & render the pre-processed template (sentinels are plain text — not Mustache tags).
            String rendered = Mustache.compiler()
                    .defaultValue("")
                    .escapeHTML(false)
                    .compile(pre.toString())
                    .execute(ctx);

            // 3) Restore the sentinel placeholders back to their original {{path}} literals.
            for (Map.Entry<String, String> e : literals.entrySet()) {
                rendered = rendered.replace(e.getKey(), e.getValue());
            }
            return rendered;
        } catch (Exception e) {
            log.warn("MustacheRenderer: template render failed, returning raw template: {}", e.getMessage());
            return template;
        }
    }

    /**
     * Resolves a dotted path like "alert.state" against a nested Map context.
     * Returns null if any segment is missing or the value is null.
     */
    @SuppressWarnings("unchecked")
    Object resolvePath(Map<String, Object> ctx, String path) {
        if (ctx == null || path == null || path.isBlank()) 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;
    }
}
