use flate2::{Compression, write::GzEncoder};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct ConstitutionGraph {
    nodes: HashMap<String, ConstitutionNode>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct ConstitutionNode {
    title: String,
    category: String,
    dependencies: Vec<String>,
    content: Value,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo:rerun-if-changed=assets/constitution.json");
    println!("cargo:rerun-if-changed=build/compress_constitution.rs");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
    let out_dir = env::var("OUT_DIR")?;
    let json_path = Path::new(&manifest_dir).join("assets/constitution.json");

    if !json_path.exists() {
        panic!(
            "assets/constitution.json not found at {}",
            json_path.display()
        );
    }

    let json_content = fs::read_to_string(&json_path)?;
    let graph: ConstitutionGraph = serde_json::from_str(&json_content)?;

    generate_rust_module(&graph, Path::new(&out_dir))?;

    Ok(())
}

fn gzip_compress(data: &[u8]) -> Vec<u8> {
    let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
    encoder.write_all(data).unwrap();
    encoder.finish().unwrap()
}

fn generate_rust_module(
    graph: &ConstitutionGraph,
    out_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    let out_path = out_dir.join("constitution_compressed.rs");
    let mut file = File::create(&out_path)?;

    writeln!(file, "// Auto-generated by build/compress_constitution.rs")?;
    writeln!(file, "// Do not edit manually")?;
    writeln!(file)?;
    writeln!(file, "use flate2::read::GzDecoder;")?;
    writeln!(file, "use std::io::Read;")?;
    writeln!(file)?;
    writeln!(file, "/// Decompress gzipped constitution document")?;
    writeln!(
        file,
        "pub fn decompress(bytes: &[u8]) -> Result<String, std::io::Error> {{"
    )?;
    writeln!(file, "    let mut decoder = GzDecoder::new(bytes);")?;
    writeln!(file, "    let mut s = String::new();")?;
    writeln!(file, "    decoder.read_to_string(&mut s)?;")?;
    writeln!(file, "    Ok(s)")?;
    writeln!(file, "}}")?;
    writeln!(file)?;

    // Sort IDs for deterministic output
    let mut ids: Vec<_> = graph.nodes.keys().collect();
    ids.sort();

    // Generate include_bytes for each compressed node content
    for id in &ids {
        let node = &graph.nodes[*id];
        let struct_name = id_to_const_name(id);
        let content = serde_json::to_string(&node.content)?;
        let compressed = gzip_compress(content.as_bytes());

        writeln!(file, "/// Compressed content for: {}", id)?;
        writeln!(
            file,
            "pub const {}_GZ: &[u8] = &{:?};",
            struct_name, compressed
        )?;
    }

    writeln!(file)?;
    writeln!(file, "/// Get decompressed constitution document by ID")?;
    writeln!(
        file,
        "pub fn get_decompressed(id: &str) -> Option<String> {{"
    )?;
    writeln!(file, "    match id {{")?;

    for id in &ids {
        let struct_name = id_to_const_name(id);
        writeln!(file, "        \"{}\" => {{", id)?;
        writeln!(
            file,
            "            let mut decoder = GzDecoder::new({}_GZ);",
            struct_name
        )?;
        writeln!(file, "            let mut s = String::new();")?;
        writeln!(file, "            decoder.read_to_string(&mut s).ok()?;")?;
        writeln!(file, "            Some(s)")?;
        writeln!(file, "        }}")?;
    }

    writeln!(file, "        _ => None,")?;
    writeln!(file, "    }}")?;
    writeln!(file, "}}")?;

    // Generate list function
    writeln!(file)?;
    writeln!(file, "/// List all available constitution IDs")?;
    writeln!(file, "pub fn list_ids() -> Vec<&'static str> {{")?;
    writeln!(file, "    vec![")?;
    for id in &ids {
        writeln!(file, "        \"{}\",", id)?;
    }
    writeln!(file, "    ]")?;
    writeln!(file, "}}")?;

    // Generate graph metadata function
    writeln!(file)?;
    writeln!(
        file,
        "/// Get node metadata (category, title, dependencies)"
    )?;
    writeln!(
        file,
        "pub fn get_metadata(id: &str) -> Option<(&'static str, &'static str, Vec<&'static str>)> {{"
    )?;
    writeln!(file, "    match id {{")?;
    for id in &ids {
        let node = &graph.nodes[*id];
        let deps: Vec<String> = node
            .dependencies
            .iter()
            .map(|s| format!("\"{}\"", s))
            .collect();
        writeln!(
            file,
            "        \"{}\" => Some((\"{}\", \"{}\", vec![{}])),",
            id,
            node.category,
            node.title,
            deps.join(", ")
        )?;
    }
    writeln!(file, "        _ => None,")?;
    writeln!(file, "    }}")?;
    writeln!(file, "}}")?;

    Ok(())
}

fn id_to_const_name(id: &str) -> String {
    id.replace(['.', '/', '-'], "_")
        .chars()
        .filter(|c| c.is_alphanumeric() || *c == '_')
        .collect::<String>()
        .to_uppercase()
}
