package com.hiveops.guide.service;

import com.hiveops.guide.dto.GuideDtos.*;
import com.hiveops.guide.model.GuideDoc;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Loads guide content from markdown files on the classpath ({@code content/**\/*.md}) at startup,
 * parses YAML-ish frontmatter, and serves the nav tree + per-module views. No database.
 */
@Service
@Slf4j
public class MarkdownGuideService {

    private volatile List<GuideDoc> docs = List.of();

    /** Which audience tiers this instance serves — the per-environment cap.
     *  bcos.dev/DLX = all; production = customer,internal. */
    @org.springframework.beans.factory.annotation.Value("${guide.served-audiences:customer,internal,dev,bcos}")
    private String servedRaw;
    private volatile Set<String> served;

    private Set<String> served() {
        Set<String> s = served;
        if (s == null) {
            s = new HashSet<>();
            for (String x : servedRaw.split(",")) {
                x = x.trim().toLowerCase(Locale.ROOT);
                if (!x.isEmpty()) s.add(x);
            }
            served = s;
            log.info("Guide serving audience tiers: {}", s);
        }
        return s;
    }

    @PostConstruct
    public void load() {
        List<GuideDoc> loaded = new ArrayList<>();
        try {
            var resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath*:content/**/*.md");
            for (Resource r : resources) {
                try (var in = r.getInputStream()) {
                    GuideDoc doc = parse(new String(in.readAllBytes(), StandardCharsets.UTF_8));
                    if (doc != null) loaded.add(doc);
                    else log.warn("Guide file missing valid frontmatter, skipped: {}", r.getFilename());
                } catch (Exception e) {
                    log.warn("Failed to read guide file {}: {}", r.getFilename(), e.getMessage());
                }
            }
        } catch (IOException e) {
            log.error("Failed to scan guide markdown content", e);
        }
        this.docs = List.copyOf(loaded);
        log.info("Loaded {} guide docs across {} modules", docs.size(),
                docs.stream().map(GuideDoc::module).distinct().count());
    }

    /** frontmatter between leading '---' fences, then the markdown body. */
    private GuideDoc parse(String raw) {
        String content = raw.replace("\r\n", "\n");
        if (!content.startsWith("---\n")) return null;
        int end = content.indexOf("\n---", 4);
        if (end < 0) return null;

        Map<String, String> meta = new HashMap<>();
        for (String line : content.substring(4, end).split("\n")) {
            int colon = line.indexOf(':');
            if (colon < 0) continue;
            String key = line.substring(0, colon).trim();
            String val = line.substring(colon + 1).trim();
            // strip surrounding quotes and trailing "# comment"
            int hash = val.indexOf(" #");
            if (hash >= 0) val = val.substring(0, hash).trim();
            if (val.length() >= 2 && val.startsWith("\"") && val.endsWith("\"")) val = val.substring(1, val.length() - 1);
            if (!key.isEmpty()) meta.put(key, val);
        }

        String module = meta.get("module");
        String tab = meta.get("tab");
        if (module == null || module.isBlank() || tab == null || tab.isBlank()) return null;

        String body = content.substring(end + 4).replaceFirst("^\\n+", "").stripTrailing();
        String app = module.contains(".") ? module.substring(0, module.indexOf('.')) : module;
        String title = meta.getOrDefault("title", module);
        String audience = meta.getOrDefault("audience", "customer").toLowerCase(Locale.ROOT);
        int order = 100;
        try { order = Integer.parseInt(meta.getOrDefault("order", "100")); } catch (NumberFormatException ignored) {}

        return new GuideDoc(app, module, title, tab, order, audience, body);
    }

    /** Audience ladder: customer < internal < dev < bcos.
     *  First the environment cap (served tiers), then the caller's role.
     *  customer→all · internal/dev→MSP or BCOS admin · bcos→BCOS admin only. */
    private boolean visible(GuideDoc d, boolean includeInternal, boolean includeBcos) {
        String a = d.audience();
        if (!served().contains(a)) return false;      // environment cap (prod = customer,internal)
        return switch (a) {
            case "bcos" -> includeBcos;
            case "internal", "dev" -> includeInternal;
            default -> true;                          // customer
        };
    }

    /** apps → modules → tabs, filtered by audience. */
    public List<NavApp> nav(boolean includeInternal, boolean includeBcos) {
        Map<String, Map<String, List<GuideDoc>>> byApp = new TreeMap<>();
        for (GuideDoc d : docs) {
            if (!visible(d, includeInternal, includeBcos)) continue;
            byApp.computeIfAbsent(d.app(), k -> new TreeMap<>())
                 .computeIfAbsent(d.module(), k -> new ArrayList<>()).add(d);
        }
        List<NavApp> apps = new ArrayList<>();
        for (var appEntry : byApp.entrySet()) {
            List<NavModule> modules = new ArrayList<>();
            for (var modEntry : appEntry.getValue().entrySet()) {
                List<GuideDoc> mdocs = modEntry.getValue();
                mdocs.sort(Comparator.comparingInt(GuideDoc::order));
                List<NavTab> tabs = mdocs.stream()
                        .map(d -> new NavTab(d.tab(), d.order(), d.audience())).toList();
                modules.add(new NavModule(modEntry.getKey(), mdocs.get(0).title(), tabs));
            }
            apps.add(new NavApp(appEntry.getKey(), modules));
        }
        return apps;
    }

    /** one module with its tabs (markdown bodies), filtered by audience. */
    public Optional<ModuleView> module(String moduleKey, boolean includeInternal, boolean includeBcos) {
        List<GuideDoc> mdocs = docs.stream()
                .filter(d -> d.module().equals(moduleKey) && visible(d, includeInternal, includeBcos))
                .sorted(Comparator.comparingInt(GuideDoc::order))
                .toList();
        if (mdocs.isEmpty()) return Optional.empty();
        List<ModuleTab> tabs = mdocs.stream()
                .map(d -> new ModuleTab(d.tab(), d.order(), d.audience(), d.body())).toList();
        return Optional.of(new ModuleView(moduleKey, mdocs.get(0).title(), tabs));
    }
}
