use std::path::Path;

use compliance_core::models::graph::CodeNode;

/// A chunk of code extracted from a source file, ready for embedding
#[derive(Debug, Clone)]
pub struct CodeChunk {
    pub qualified_name: String,
    pub kind: String,
    pub file_path: String,
    pub start_line: u32,
    pub end_line: u32,
    pub language: String,
    pub content: String,
    pub context_header: String,
    pub token_estimate: u32,
}

/// Extract embeddable code chunks from parsed CodeNodes.
///
/// For each node, reads the corresponding source lines from disk,
/// builds a context header, and estimates tokens.
pub fn extract_chunks(
    repo_path: &Path,
    nodes: &[CodeNode],
    max_chunk_tokens: u32,
) -> Vec<CodeChunk> {
    let mut chunks = Vec::new();

    for node in nodes {
        let file = repo_path.join(&node.file_path);
        let source = match std::fs::read_to_string(&file) {
            Ok(s) => s,
            Err(_) => continue,
        };

        let lines: Vec<&str> = source.lines().collect();
        let start = node.start_line.saturating_sub(1) as usize;
        let end = (node.end_line as usize).min(lines.len());
        if start >= end {
            continue;
        }

        let content: String = lines[start..end].join("\n");

        // Skip tiny chunks
        if content.len() < 50 {
            continue;
        }

        // Estimate tokens (~4 chars per token)
        let mut token_estimate = (content.len() / 4) as u32;

        // Truncate if too large
        let final_content = if token_estimate > max_chunk_tokens {
            let max_chars = (max_chunk_tokens as usize) * 4;
            token_estimate = max_chunk_tokens;
            content.chars().take(max_chars).collect()
        } else {
            content
        };

        // Build context header: file path + containing scope hint
        let context_header = build_context_header(
            &node.file_path,
            &node.qualified_name,
            &node.kind.to_string(),
        );

        chunks.push(CodeChunk {
            qualified_name: node.qualified_name.clone(),
            kind: node.kind.to_string(),
            file_path: node.file_path.clone(),
            start_line: node.start_line,
            end_line: node.end_line,
            language: node.language.clone(),
            content: final_content,
            context_header,
            token_estimate,
        });
    }

    chunks
}

fn build_context_header(file_path: &str, qualified_name: &str, kind: &str) -> String {
    // Extract containing module/class from qualified name
    // e.g. "src/main.rs::MyStruct::my_method" → parent is "MyStruct"
    let parts: Vec<&str> = qualified_name.split("::").collect();
    if parts.len() >= 2 {
        let parent = parts[..parts.len() - 1].join("::");
        format!("// {file_path} | {kind} in {parent}")
    } else {
        format!("// {file_path} | {kind}")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_context_header_with_parent() {
        let result =
            build_context_header("src/main.rs", "src/main.rs::MyStruct::my_method", "method");
        assert_eq!(result, "// src/main.rs | method in src/main.rs::MyStruct");
    }

    #[test]
    fn test_build_context_header_top_level() {
        let result = build_context_header("src/lib.rs", "main", "function");
        assert_eq!(result, "// src/lib.rs | function");
    }

    #[test]
    fn test_build_context_header_single_parent() {
        let result = build_context_header("src/lib.rs", "src/lib.rs::do_stuff", "function");
        assert_eq!(result, "// src/lib.rs | function in src/lib.rs");
    }

    #[test]
    fn test_build_context_header_deep_nesting() {
        let result = build_context_header(
            "src/mod.rs",
            "src/mod.rs::Outer::Inner::deep_fn",
            "function",
        );
        assert_eq!(
            result,
            "// src/mod.rs | function in src/mod.rs::Outer::Inner"
        );
    }

    #[test]
    fn test_build_context_header_empty_strings() {
        let result = build_context_header("", "", "function");
        assert_eq!(result, "//  | function");
    }

    #[test]
    fn test_code_chunk_struct_fields() {
        let chunk = CodeChunk {
            qualified_name: "main".to_string(),
            kind: "function".to_string(),
            file_path: "src/main.rs".to_string(),
            start_line: 1,
            end_line: 10,
            language: "rust".to_string(),
            content: "fn main() {}".to_string(),
            context_header: "// src/main.rs | function".to_string(),
            token_estimate: 3,
        };
        assert_eq!(chunk.start_line, 1);
        assert_eq!(chunk.end_line, 10);
        assert_eq!(chunk.language, "rust");
    }
}
