use std::path::Path;

use compliance_core::error::CoreError;
use compliance_core::models::graph::{CodeEdge, CodeEdgeKind, CodeNode, CodeNodeKind};
use compliance_core::traits::graph_builder::{LanguageParser, ParseOutput};
use tree_sitter::{Node, Parser};

pub struct PythonParser;

impl Default for PythonParser {
    fn default() -> Self {
        Self::new()
    }
}

impl PythonParser {
    pub fn new() -> Self {
        Self
    }

    fn walk_tree(
        &self,
        node: Node<'_>,
        source: &str,
        file_path: &str,
        repo_id: &str,
        graph_build_id: &str,
        parent_qualified: Option<&str>,
        output: &mut ParseOutput,
    ) {
        match node.kind() {
            "function_definition" => {
                if let Some(name_node) = node.child_by_field_name("name") {
                    let name = &source[name_node.byte_range()];
                    let qualified = match parent_qualified {
                        Some(p) => format!("{p}.{name}"),
                        None => format!("{file_path}::{name}"),
                    };

                    let is_method = parent_qualified
                        .map(|p| p.contains("class"))
                        .unwrap_or(false);
                    let kind = if is_method {
                        CodeNodeKind::Method
                    } else {
                        CodeNodeKind::Function
                    };

                    let is_entry = name == "__main__"
                        || name == "main"
                        || self.has_decorator(&node, source, "app.route")
                        || self.has_decorator(&node, source, "app.get")
                        || self.has_decorator(&node, source, "app.post");

                    output.nodes.push(CodeNode {
                        id: None,
                        repo_id: repo_id.to_string(),
                        graph_build_id: graph_build_id.to_string(),
                        qualified_name: qualified.clone(),
                        name: name.to_string(),
                        kind,
                        file_path: file_path.to_string(),
                        start_line: node.start_position().row as u32 + 1,
                        end_line: node.end_position().row as u32 + 1,
                        language: "python".to_string(),
                        community_id: None,
                        is_entry_point: is_entry,
                        graph_index: None,
                    });

                    // Extract calls in function body
                    if let Some(body) = node.child_by_field_name("body") {
                        self.extract_calls(
                            body,
                            source,
                            file_path,
                            repo_id,
                            graph_build_id,
                            &qualified,
                            output,
                        );
                    }
                }
            }
            "class_definition" => {
                if let Some(name_node) = node.child_by_field_name("name") {
                    let name = &source[name_node.byte_range()];
                    let qualified = match parent_qualified {
                        Some(p) => format!("{p}.{name}"),
                        None => format!("{file_path}::{name}"),
                    };

                    output.nodes.push(CodeNode {
                        id: None,
                        repo_id: repo_id.to_string(),
                        graph_build_id: graph_build_id.to_string(),
                        qualified_name: qualified.clone(),
                        name: name.to_string(),
                        kind: CodeNodeKind::Class,
                        file_path: file_path.to_string(),
                        start_line: node.start_position().row as u32 + 1,
                        end_line: node.end_position().row as u32 + 1,
                        language: "python".to_string(),
                        community_id: None,
                        is_entry_point: false,
                        graph_index: None,
                    });

                    // Extract superclasses
                    if let Some(bases) = node.child_by_field_name("superclasses") {
                        self.extract_inheritance(
                            bases,
                            source,
                            file_path,
                            repo_id,
                            graph_build_id,
                            &qualified,
                            output,
                        );
                    }

                    // Walk methods
                    if let Some(body) = node.child_by_field_name("body") {
                        self.walk_children(
                            body,
                            source,
                            file_path,
                            repo_id,
                            graph_build_id,
                            Some(&qualified),
                            output,
                        );
                    }
                    return;
                }
            }
            "import_statement" | "import_from_statement" => {
                let import_text = &source[node.byte_range()];
                if let Some(module) = self.extract_import_module(import_text) {
                    output.edges.push(CodeEdge {
                        id: None,
                        repo_id: repo_id.to_string(),
                        graph_build_id: graph_build_id.to_string(),
                        source: parent_qualified.unwrap_or(file_path).to_string(),
                        target: module,
                        kind: CodeEdgeKind::Imports,
                        file_path: file_path.to_string(),
                        line_number: Some(node.start_position().row as u32 + 1),
                    });
                }
            }
            _ => {}
        }

        self.walk_children(
            node,
            source,
            file_path,
            repo_id,
            graph_build_id,
            parent_qualified,
            output,
        );
    }

    fn walk_children(
        &self,
        node: Node<'_>,
        source: &str,
        file_path: &str,
        repo_id: &str,
        graph_build_id: &str,
        parent_qualified: Option<&str>,
        output: &mut ParseOutput,
    ) {
        let mut cursor = node.walk();
        for child in node.children(&mut cursor) {
            self.walk_tree(
                child,
                source,
                file_path,
                repo_id,
                graph_build_id,
                parent_qualified,
                output,
            );
        }
    }

    fn extract_calls(
        &self,
        node: Node<'_>,
        source: &str,
        file_path: &str,
        repo_id: &str,
        graph_build_id: &str,
        caller_qualified: &str,
        output: &mut ParseOutput,
    ) {
        if node.kind() == "call" {
            if let Some(func_node) = node.child_by_field_name("function") {
                let callee = &source[func_node.byte_range()];
                output.edges.push(CodeEdge {
                    id: None,
                    repo_id: repo_id.to_string(),
                    graph_build_id: graph_build_id.to_string(),
                    source: caller_qualified.to_string(),
                    target: callee.to_string(),
                    kind: CodeEdgeKind::Calls,
                    file_path: file_path.to_string(),
                    line_number: Some(node.start_position().row as u32 + 1),
                });
            }
        }

        let mut cursor = node.walk();
        for child in node.children(&mut cursor) {
            self.extract_calls(
                child,
                source,
                file_path,
                repo_id,
                graph_build_id,
                caller_qualified,
                output,
            );
        }
    }

    fn extract_inheritance(
        &self,
        node: Node<'_>,
        source: &str,
        file_path: &str,
        repo_id: &str,
        graph_build_id: &str,
        class_qualified: &str,
        output: &mut ParseOutput,
    ) {
        let mut cursor = node.walk();
        for child in node.children(&mut cursor) {
            if child.kind() == "identifier" || child.kind() == "attribute" {
                let base_name = &source[child.byte_range()];
                output.edges.push(CodeEdge {
                    id: None,
                    repo_id: repo_id.to_string(),
                    graph_build_id: graph_build_id.to_string(),
                    source: class_qualified.to_string(),
                    target: base_name.to_string(),
                    kind: CodeEdgeKind::Inherits,
                    file_path: file_path.to_string(),
                    line_number: Some(node.start_position().row as u32 + 1),
                });
            }
        }
    }

    fn has_decorator(&self, node: &Node<'_>, source: &str, decorator_name: &str) -> bool {
        if let Some(prev) = node.prev_sibling() {
            if prev.kind() == "decorator" {
                let text = &source[prev.byte_range()];
                return text.contains(decorator_name);
            }
        }
        false
    }

    fn extract_import_module(&self, import_text: &str) -> Option<String> {
        if let Some(rest) = import_text.strip_prefix("from ") {
            // "from foo.bar import baz" -> "foo.bar"
            let module = rest.split_whitespace().next()?;
            Some(module.to_string())
        } else if let Some(rest) = import_text.strip_prefix("import ") {
            let module = rest.trim().trim_end_matches(';');
            Some(module.to_string())
        } else {
            None
        }
    }
}

impl LanguageParser for PythonParser {
    fn language(&self) -> &str {
        "python"
    }

    fn extensions(&self) -> &[&str] {
        &["py"]
    }

    fn parse_file(
        &self,
        file_path: &Path,
        source: &str,
        repo_id: &str,
        graph_build_id: &str,
    ) -> Result<ParseOutput, CoreError> {
        let mut parser = Parser::new();
        let language = tree_sitter_python::LANGUAGE;
        parser
            .set_language(&language.into())
            .map_err(|e| CoreError::Graph(format!("Failed to set Python language: {e}")))?;

        let tree = parser
            .parse(source, None)
            .ok_or_else(|| CoreError::Graph("Failed to parse Python file".to_string()))?;

        let file_path_str = file_path.to_string_lossy().to_string();
        let mut output = ParseOutput::default();

        output.nodes.push(CodeNode {
            id: None,
            repo_id: repo_id.to_string(),
            graph_build_id: graph_build_id.to_string(),
            qualified_name: file_path_str.clone(),
            name: file_path
                .file_name()
                .map(|n| n.to_string_lossy().to_string())
                .unwrap_or_default(),
            kind: CodeNodeKind::File,
            file_path: file_path_str.clone(),
            start_line: 1,
            end_line: source.lines().count() as u32,
            language: "python".to_string(),
            community_id: None,
            is_entry_point: false,
            graph_index: None,
        });

        self.walk_tree(
            tree.root_node(),
            source,
            &file_path_str,
            repo_id,
            graph_build_id,
            None,
            &mut output,
        );

        Ok(output)
    }
}
