use quote::quote;
use std::{env, fs, path::PathBuf};
use syn::{Fields, Item, ItemStruct, Type, Visibility, parse_file};

fn main() {
    println!("cargo:rerun-if-changed=src/chunks.rs");
    println!("cargo:rerun-if-changed=src/lib.rs");
    println!("cargo:rerun-if-changed=src/errors.rs");
    println!("cargo:rerun-if-changed=src/handlers.rs");

    // Generate type stubs
    let mut pyi_content = String::new();

    // Add header
    pyi_content.push_str("# Auto-generated type stubs for teehistorian_py._rust\n");
    pyi_content.push_str("# DO NOT EDIT - Generated by build.rs\n\n");
    pyi_content.push_str("from typing import Any, Iterator, Optional, List, Dict, Union\n\n");

    // Add version and doc
    pyi_content.push_str("__version__: str\n");
    pyi_content.push_str("__doc__: str\n\n");

    // Add exception class
    pyi_content.push_str("class TeehistorianError(Exception):\n");
    pyi_content.push_str("    \"\"\"Base exception for teehistorian errors\"\"\"\n");
    pyi_content.push_str("    def __init__(self, message: str) -> None: ...\n\n");

    // Add main Teehistorian class
    pyi_content.push_str("class Teehistorian:\n");
    pyi_content.push_str("    \"\"\"Main teehistorian parser class\"\"\"\n");
    pyi_content.push_str("    \n");
    pyi_content.push_str("    def __init__(self, data: bytes) -> None:\n");
    pyi_content.push_str("        \"\"\"Initialize parser with raw data\n");
    pyi_content.push_str("        \n");
    pyi_content.push_str("        Args:\n");
    pyi_content.push_str("            data: Raw teehistorian file data\n");
    pyi_content.push_str("        \n");
    pyi_content.push_str("        Raises:\n");
    pyi_content.push_str("            TeehistorianError: If initialization fails\n");
    pyi_content.push_str("        \"\"\"\n");
    pyi_content.push_str("        ...\n");
    pyi_content.push_str("    \n");
    pyi_content.push_str("    def register_custom_uuid(self, uuid_string: str) -> None:\n");
    pyi_content.push_str("        \"\"\"Register a custom UUID handler\n");
    pyi_content.push_str("        \n");
    pyi_content.push_str("        Args:\n");
    pyi_content.push_str("            uuid_string: UUID to register\n");
    pyi_content.push_str("        \"\"\"\n");
    pyi_content.push_str("        ...\n");
    pyi_content.push_str("    \n");
    pyi_content.push_str("    def header(self) -> bytes:\n");
    pyi_content.push_str("        \"\"\"Get header data as bytes\"\"\"\n");
    pyi_content.push_str("        ...\n");
    pyi_content.push_str("    \n");
    pyi_content.push_str("    @property\n");
    pyi_content.push_str("    def chunk_count(self) -> int:\n");
    pyi_content.push_str("        \"\"\"Get current chunk count\"\"\"\n");
    pyi_content.push_str("        ...\n");
    pyi_content.push_str("    \n");
    pyi_content.push_str("    def get_registered_uuids(self) -> List[str]:\n");
    pyi_content.push_str("        \"\"\"Get list of registered UUIDs\"\"\"\n");
    pyi_content.push_str("        ...\n");
    pyi_content.push_str("    \n");
    pyi_content.push_str("    def __iter__(self) -> 'Teehistorian': ...\n");
    pyi_content.push_str("    def __next__(self) -> 'Chunk': ...\n\n");

    // Parse chunks.rs to extract chunk classes
    let chunks_path = PathBuf::from("src/chunks.rs");
    if let Ok(content) = fs::read_to_string(&chunks_path)
        && let Ok(file) = parse_file(&content)
    {
        // Add base Chunk class
        pyi_content.push_str("class Chunk:\n");
        pyi_content.push_str("    \"\"\"Base class for all chunk types\"\"\"\n");
        pyi_content.push_str("    def chunk_type(self) -> str: ...\n");
        pyi_content.push_str("    def __repr__(self) -> str: ...\n");
        pyi_content.push_str("    def __str__(self) -> str: ...\n");
        pyi_content.push_str("    def to_dict(self) -> Dict[str, Any]: ...\n\n");

        // Process each struct in the file
        for item in file.items {
            if let Item::Struct(item_struct) = item {
                // Only process PyXXX structs that are public and have pyclass attribute
                if let Some(class_def) = extract_pyclass(&item_struct) {
                    pyi_content.push_str(&class_def);
                }
            }
        }
    }

    // Add type unions for convenience
    pyi_content.push_str("\n# Type unions for easier type checking\n");
    pyi_content.push_str("PlayerChunk = Union[\n");
    pyi_content.push_str("    Join, JoinVer6, Drop, PlayerReady, PlayerNew, PlayerOld,\n");
    pyi_content.push_str("    PlayerTeam, PlayerName, PlayerDiff, AuthLogin, DdnetVersion,\n");
    pyi_content.push_str("    ConsoleCommand, NetMessage, InputNew, InputDiff\n");
    pyi_content.push_str("]\n\n");

    pyi_content.push_str("ServerChunk = Union[\n");
    pyi_content.push_str("    TickSkip, TeamLoadSuccess, TeamLoadFailure, AntiBot, Eos\n");
    pyi_content.push_str("]\n\n");

    pyi_content.push_str("CustomChunkTypes = Union[CustomChunk, Unknown, Generic]\n\n");
    pyi_content.push_str("AnyChunk = Union[PlayerChunk, ServerChunk, CustomChunkTypes]\n\n");

    // Write the .pyi file to the output directory
    let out_dir = env::var("OUT_DIR").unwrap();
    let pyi_path = PathBuf::from(&out_dir).join("_rust.pyi");
    fs::write(&pyi_path, pyi_content).expect("Failed to write .pyi file");

    println!(
        "cargo:warning=Generated type stubs at {}",
        pyi_path.display()
    );
}

fn extract_pyclass(item_struct: &ItemStruct) -> Option<String> {
    // Check if struct has pyclass attribute
    let has_pyclass = item_struct
        .attrs
        .iter()
        .any(|attr| attr.path().is_ident("pyclass"));

    if !has_pyclass {
        return None;
    }

    // Skip if not public
    if !matches!(item_struct.vis, Visibility::Public(_)) {
        return None;
    }

    let struct_name = &item_struct.ident;

    // Skip base PyChunk class
    if struct_name == "PyChunk" {
        return None;
    }

    // Get the Python class name (remove Py prefix if present)
    let class_name = struct_name
        .to_string()
        .strip_prefix("Py")
        .unwrap_or(&struct_name.to_string())
        .to_string();

    let mut class_def = String::new();

    // Add class definition
    class_def.push_str(&format!("class {}(Chunk):\n", class_name));

    // Extract fields and generate properties
    if let Fields::Named(fields) = &item_struct.fields {
        for field in &fields.named {
            if let Some(field_name) = &field.ident {
                // Check if field has pyo3(get) attribute
                let has_getter = field.attrs.iter().any(|attr| {
                    if attr.path().is_ident("pyo3") {
                        // Simple check - in real code would parse the attribute properly
                        true
                    } else {
                        false
                    }
                });

                if has_getter {
                    let py_type = rust_type_to_python(&field.ty);
                    class_def.push_str(&format!("    {}: {}\n", field_name, py_type));
                }
            }
        }

        // Generate __init__ method
        class_def.push_str("    def __init__(self");

        // Add constructor parameters
        let params: Vec<String> = fields
            .named
            .iter()
            .filter_map(|field| {
                field.ident.as_ref().map(|name| {
                    let py_type = rust_type_to_python(&field.ty);
                    format!(", {}: {}", name, py_type)
                })
            })
            .collect();

        for param in params {
            class_def.push_str(&param);
        }

        class_def.push_str(") -> None: ...\n");
    } else if let Fields::Unit = &item_struct.fields {
        // Unit struct (no fields)
        class_def.push_str("    def __init__(self) -> None: ...\n");
    }

    // Add common methods
    class_def.push_str("    def __repr__(self) -> str: ...\n");
    class_def.push_str("    def __str__(self) -> str: ...\n");
    class_def.push_str("    def to_dict(self) -> Dict[str, Any]: ...\n");

    // Special methods for specific types
    if class_name == "Unknown" || class_name == "CustomChunk" {
        class_def.push_str("    def data_preview(self) -> str:\n");
        class_def.push_str("        \"\"\"Get hex preview of data (first 32 bytes)\"\"\"\n");
        class_def.push_str("        ...\n");
    }

    class_def.push('\n');

    Some(class_def)
}

fn rust_type_to_python(ty: &Type) -> String {
    let type_str = quote!(#ty).to_string();

    // Remove extra spaces that quote! might add
    let type_str = type_str.replace(" ", "");

    match type_str.as_str() {
        "i8" | "i16" | "i32" | "i64" | "i128" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize"
        | "isize" => "int".to_string(),

        "f32" | "f64" => "float".to_string(),

        "bool" => "bool".to_string(),

        "String" | "&str" | "str" => "str".to_string(),

        "Vec<u8>" | "&[u8]" => "bytes".to_string(),

        s if s.starts_with("Vec<i") && s.ends_with(">") => "List[int]".to_string(),
        s if s.starts_with("Vec<") => "List[Any]".to_string(),

        s if s.starts_with("Option<") => {
            let inner = s.trim_start_matches("Option<").trim_end_matches(">");
            format!("Optional[{}]", rust_type_to_python_str(inner))
        }

        s if s.starts_with("HashMap<") || s.starts_with("BTreeMap<") => {
            "Dict[Any, Any]".to_string()
        }

        _ => "Any".to_string(),
    }
}

fn rust_type_to_python_str(type_str: &str) -> String {
    match type_str {
        "i8" | "i16" | "i32" | "i64" | "i128" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize"
        | "isize" => "int".to_string(),

        "f32" | "f64" => "float".to_string(),
        "bool" => "bool".to_string(),
        "String" | "&str" | "str" => "str".to_string(),

        _ => "Any".to_string(),
    }
}
