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 TypeScriptParser;

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

impl TypeScriptParser {
    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_declaration" => {
                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::Function,
                        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: "typescript".to_string(),
                        community_id: None,
                        is_entry_point: self.is_exported(&node),
                        graph_index: None,
                    });

                    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_declaration" => {
                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: "typescript".to_string(),
                        community_id: None,
                        is_entry_point: false,
                        graph_index: None,
                    });

                    // Heritage clause (extends/implements)
                    self.extract_heritage(
                        &node,
                        source,
                        file_path,
                        repo_id,
                        graph_build_id,
                        &qualified,
                        output,
                    );

                    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;
                }
            }
            "interface_declaration" => {
                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::Interface,
                        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: "typescript".to_string(),
                        community_id: None,
                        is_entry_point: false,
                        graph_index: None,
                    });
                }
            }
            "method_definition" | "public_field_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::Method,
                        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: "typescript".to_string(),
                        community_id: None,
                        is_entry_point: false,
                        graph_index: None,
                    });

                    if let Some(body) = node.child_by_field_name("body") {
                        self.extract_calls(
                            body,
                            source,
                            file_path,
                            repo_id,
                            graph_build_id,
                            &qualified,
                            output,
                        );
                    }
                }
            }
            "lexical_declaration" | "variable_declaration" => {
                self.extract_arrow_functions(
                    node,
                    source,
                    file_path,
                    repo_id,
                    graph_build_id,
                    parent_qualified,
                    output,
                );
            }
            "import_statement" => {
                let text = &source[node.byte_range()];
                if let Some(module) = self.extract_import_source(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_expression" {
            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_arrow_functions(
        &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) {
            if child.kind() == "variable_declarator" {
                let name_node = child.child_by_field_name("name");
                let value_node = child.child_by_field_name("value");
                if let (Some(name_n), Some(value_n)) = (name_node, value_node) {
                    if value_n.kind() == "arrow_function" || value_n.kind() == "function" {
                        let name = &source[name_n.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::Function,
                            file_path: file_path.to_string(),
                            start_line: child.start_position().row as u32 + 1,
                            end_line: child.end_position().row as u32 + 1,
                            language: "typescript".to_string(),
                            community_id: None,
                            is_entry_point: false,
                            graph_index: None,
                        });

                        if let Some(body) = value_n.child_by_field_name("body") {
                            self.extract_calls(
                                body,
                                source,
                                file_path,
                                repo_id,
                                graph_build_id,
                                &qualified,
                                output,
                            );
                        }
                    }
                }
            }
        }
    }

    fn extract_heritage(
        &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() == "class_heritage" {
                let text = &source[child.byte_range()];
                // "extends Base implements IFoo, IBar"
                if let Some(rest) = text.strip_prefix("extends ") {
                    let base = rest.split_whitespace().next().unwrap_or(rest);
                    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.trim_matches(',').to_string(),
                        kind: CodeEdgeKind::Inherits,
                        file_path: file_path.to_string(),
                        line_number: Some(child.start_position().row as u32 + 1),
                    });
                }
                if text.contains("implements ") {
                    if let Some(impl_part) = text.split("implements ").nth(1) {
                        for iface in impl_part.split(',') {
                            let iface = iface.trim();
                            if !iface.is_empty() {
                                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: iface.to_string(),
                                    kind: CodeEdgeKind::Implements,
                                    file_path: file_path.to_string(),
                                    line_number: Some(child.start_position().row as u32 + 1),
                                });
                            }
                        }
                    }
                }
            }
        }
    }

    fn is_exported(&self, node: &Node<'_>) -> bool {
        if let Some(parent) = node.parent() {
            return parent.kind() == "export_statement";
        }
        false
    }

    fn extract_import_source(&self, import_text: &str) -> Option<String> {
        let from_idx = import_text.find("from ");
        let start = if let Some(idx) = from_idx {
            idx + 5
        } else {
            import_text.find("import ")? + 7
        };
        let rest = &import_text[start..];
        let module = rest
            .trim()
            .trim_matches(|c| c == '\'' || c == '"' || c == ';' || c == ' ');
        if module.is_empty() {
            None
        } else {
            Some(module.to_string())
        }
    }
}

impl LanguageParser for TypeScriptParser {
    fn language(&self) -> &str {
        "typescript"
    }

    fn extensions(&self) -> &[&str] {
        &["ts", "tsx"]
    }

    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_typescript::LANGUAGE_TYPESCRIPT;
        parser
            .set_language(&language.into())
            .map_err(|e| CoreError::Graph(format!("Failed to set TypeScript language: {e}")))?;

        let tree = parser
            .parse(source, None)
            .ok_or_else(|| CoreError::Graph("Failed to parse TypeScript 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: "typescript".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)
    }
}
