use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

use serde::Serialize;

use eidos_kernel::schema::{Graph, Kind};

use super::frontmatter::{has_malformed_frontmatter, parse_frontmatter};
use super::helpers::first_body_line;
use super::quality::{
    DiagnosticSeverity, NodeQualityScore, QualityAuditOptions, QualityFinding,
    audit_quality_with_options,
};
use super::relations::default_relation_fields;
use super::scan::aun_source_markdown;

#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct CompileDiagnostics {
    pub markdown_files_scanned: usize,
    pub malformed_frontmatter: usize,
    pub nodes: usize,
    pub edges: usize,
    pub nodes_by_kind: BTreeMap<String, usize>,
    pub edges_by_relation: BTreeMap<String, usize>,
    pub warnings: Vec<String>,
    pub findings: Vec<QualityFinding>,
    pub quality_scores: Vec<NodeQualityScore>,
}

fn looks_empty_markdown(text: &str) -> bool {
    let (fm, body) = parse_frontmatter(text);
    fm.get("summary").is_none()
        && fm.get("description").is_none()
        && first_body_line(body).is_empty()
}

fn kind_label(kind: &Kind) -> String {
    serde_json::to_value(kind)
        .ok()
        .and_then(|v| v.as_str().map(String::from))
        .unwrap_or_else(|| "unknown".into())
}

pub fn diagnostics_for(root: &Path, codex_root: &Path, graph: &Graph) -> CompileDiagnostics {
    diagnostics_for_with_relation_fields(root, codex_root, graph, &default_relation_fields())
}

pub fn diagnostics_for_with_relation_fields(
    root: &Path,
    codex_root: &Path,
    graph: &Graph,
    relation_fields: &BTreeMap<String, String>,
) -> CompileDiagnostics {
    diagnostics_for_with_options(
        root,
        codex_root,
        graph,
        relation_fields,
        QualityAuditOptions::default(),
    )
}

pub fn diagnostics_for_registry_source(
    root: &Path,
    codex_root: &Path,
    graph: &Graph,
    relation_fields: &BTreeMap<String, String>,
) -> CompileDiagnostics {
    diagnostics_for_with_options(
        root,
        codex_root,
        graph,
        relation_fields,
        QualityAuditOptions {
            validate_relation_targets: false,
        },
    )
}

fn diagnostics_for_with_options(
    root: &Path,
    codex_root: &Path,
    graph: &Graph,
    relation_fields: &BTreeMap<String, String>,
    quality_options: QualityAuditOptions,
) -> CompileDiagnostics {
    let markdown = aun_source_markdown(root, codex_root);
    let malformed_frontmatter = markdown
        .iter()
        .filter(|path| fs::read_to_string(path).is_ok_and(|text| has_malformed_frontmatter(&text)))
        .count();
    let empty_markdown = markdown
        .iter()
        .filter(|path| fs::read_to_string(path).is_ok_and(|text| looks_empty_markdown(&text)))
        .count();

    let mut nodes_by_kind = BTreeMap::new();
    for node in &graph.nodes {
        *nodes_by_kind.entry(kind_label(&node.kind)).or_default() += 1;
    }

    let mut edges_by_relation = BTreeMap::new();
    for edge in &graph.edges {
        *edges_by_relation.entry(edge.relation.clone()).or_default() += 1;
    }

    let mut warnings = Vec::new();
    if markdown.is_empty() {
        warnings.push("no markdown files found under vault/skills or docs root".into());
    }
    if graph.nodes.is_empty() {
        warnings.push("compiled graph is empty".into());
    }
    if malformed_frontmatter > 0 {
        warnings.push(format!(
            "{malformed_frontmatter} markdown file(s) have unterminated frontmatter"
        ));
    }
    if empty_markdown > 0 {
        warnings.push(format!(
            "{empty_markdown} markdown file(s) have no summary/description or body text"
        ));
    }

    let (findings, quality_scores) =
        audit_quality_with_options(&markdown, graph, relation_fields, quality_options);
    let warning_findings = findings
        .iter()
        .filter(|f| f.severity == DiagnosticSeverity::Warning)
        .count();
    if warning_findings > 0 {
        warnings.push(format!(
            "{warning_findings} vault quality warning finding(s)"
        ));
    }

    CompileDiagnostics {
        markdown_files_scanned: markdown.len(),
        malformed_frontmatter,
        nodes: graph.nodes.len(),
        edges: graph.edges.len(),
        nodes_by_kind,
        edges_by_relation,
        warnings,
        findings,
        quality_scores,
    }
}
