use std::collections::HashMap;
use std::path::Path;

use crate::analyze::god_nodes::{self, GodNode};
use crate::graph::KodexGraph;

/// Generate Wikipedia-style wiki: index.md + community articles + god node articles.
pub fn to_wiki(
    graph: &KodexGraph,
    communities: &HashMap<usize, Vec<String>>,
    output_dir: &Path,
    community_labels: Option<&HashMap<usize, String>>,
    cohesion: Option<&HashMap<usize, f64>>,
    god_nodes_data: Option<&[GodNode]>,
) -> std::io::Result<usize> {
    std::fs::create_dir_all(output_dir)?;

    let default_gods = god_nodes::god_nodes(graph, 10);
    let gods = god_nodes_data.unwrap_or(&default_gods);

    let total_nodes = graph.node_count();
    let total_edges = graph.edge_count();
    let mut written = 0;

    // Sort communities by size descending
    let mut sorted_cids: Vec<usize> = communities.keys().copied().collect();
    sorted_cids.sort_by(|a, b| {
        let la = communities.get(b).map(|v| v.len()).unwrap_or(0);
        let lb = communities.get(a).map(|v| v.len()).unwrap_or(0);
        la.cmp(&lb)
    });

    // Index
    let mut index = String::new();
    index.push_str("# Knowledge Graph Index\n\n");
    index.push_str("> Auto-generated by kodex\n\n");
    index.push_str(&format!(
        "**{total_nodes} nodes \u{00b7} {total_edges} edges \u{00b7} {} communities**\n\n",
        communities.len()
    ));

    index.push_str("## Communities\n\n");
    for &cid in &sorted_cids {
        let label = community_labels
            .and_then(|cl| cl.get(&cid))
            .cloned()
            .unwrap_or_else(|| format!("Community {cid}"));
        let count = communities.get(&cid).map(|v| v.len()).unwrap_or(0);
        let fname = safe_filename(&label);
        index.push_str(&format!("- [[{fname}]] \u{2014} {count} nodes\n"));
    }

    if !gods.is_empty() {
        index.push_str("\n## God Nodes\n\n");
        for god in gods {
            let fname = safe_filename(&god.label);
            index.push_str(&format!(
                "- [[{fname}]] \u{2014} {} connections\n",
                god.degree
            ));
        }
    }

    std::fs::write(output_dir.join("index.md"), index)?;

    // Community articles
    for &cid in &sorted_cids {
        let nodes = match communities.get(&cid) {
            Some(n) => n,
            None => continue,
        };
        let label = community_labels
            .and_then(|cl| cl.get(&cid))
            .cloned()
            .unwrap_or_else(|| format!("Community {cid}"));
        let coh = cohesion.and_then(|c| c.get(&cid)).copied().unwrap_or(0.0);

        let mut md = String::new();
        md.push_str(&format!("# {label}\n\n"));
        md.push_str(&format!(
            "> {} nodes \u{00b7} cohesion {coh:.2}\n\n",
            nodes.len()
        ));

        // Key concepts (top 25 by degree)
        md.push_str("## Key Concepts\n\n");
        let mut node_degrees: Vec<(&str, usize)> = nodes
            .iter()
            .map(|nid| (nid.as_str(), graph.degree(nid)))
            .collect();
        node_degrees.sort_by(|a, b| b.1.cmp(&a.1));
        for (nid, deg) in node_degrees.iter().take(25) {
            if let Some(node) = graph.get_node(nid) {
                md.push_str(&format!(
                    "- **{}** ({deg} connections) \u{2014} `{}`\n",
                    node.label, node.source_file
                ));
            }
        }

        // Confidence breakdown
        let mut ext = 0usize;
        let mut inf = 0usize;
        let mut amb = 0usize;
        for (src, tgt, edge) in graph.edges() {
            if nodes.contains(&src.to_string()) || nodes.contains(&tgt.to_string()) {
                match edge.confidence {
                    crate::types::Confidence::EXTRACTED => ext += 1,
                    crate::types::Confidence::INFERRED => inf += 1,
                    crate::types::Confidence::AMBIGUOUS => amb += 1,
                }
            }
        }
        let total = ext + inf + amb;
        if total > 0 {
            md.push_str("\n## Audit Trail\n\n");
            md.push_str(&format!(
                "- EXTRACTED: {ext} ({:.0}%)\n",
                ext as f64 / total as f64 * 100.0
            ));
            md.push_str(&format!(
                "- INFERRED: {inf} ({:.0}%)\n",
                inf as f64 / total as f64 * 100.0
            ));
            md.push_str(&format!(
                "- AMBIGUOUS: {amb} ({:.0}%)\n",
                amb as f64 / total as f64 * 100.0
            ));
        }

        let fname = safe_filename(&label);
        std::fs::write(output_dir.join(format!("{fname}.md")), md)?;
        written += 1;
    }

    // God node articles
    for god in gods {
        let mut md = String::new();
        md.push_str(&format!("# {}\n\n", god.label));
        md.push_str(&format!(
            "> God node \u{00b7} {} connections \u{00b7} `{}`\n\n",
            god.degree, god.source_file
        ));

        // Group neighbors by relation
        let mut by_relation: HashMap<String, Vec<String>> = HashMap::new();
        for (src, tgt, edge) in graph.edges() {
            if src == god.id {
                if let Some(n) = graph.get_node(tgt) {
                    by_relation
                        .entry(edge.relation.clone())
                        .or_default()
                        .push(format!(
                            "[[{}]] `{}`",
                            safe_filename(&n.label),
                            edge.confidence
                        ));
                }
            } else if tgt == god.id {
                if let Some(n) = graph.get_node(src) {
                    by_relation
                        .entry(edge.relation.clone())
                        .or_default()
                        .push(format!(
                            "[[{}]] `{}`",
                            safe_filename(&n.label),
                            edge.confidence
                        ));
                }
            }
        }

        if !by_relation.is_empty() {
            md.push_str("## Connections by Relation\n\n");
            let mut rels: Vec<_> = by_relation.into_iter().collect();
            rels.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
            for (rel, neighbors) in rels {
                md.push_str(&format!("### {rel}\n\n"));
                for n in neighbors {
                    md.push_str(&format!("- {n}\n"));
                }
                md.push('\n');
            }
        }

        let fname = safe_filename(&god.label);
        std::fs::write(output_dir.join(format!("{fname}.md")), md)?;
        written += 1;
    }

    Ok(written)
}

fn safe_filename(s: &str) -> String {
    s.replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|', ' '], "_")
}
