"""Traversal tools for navigating the code graph."""

from emdash.agent.tools.base import BaseTool, ToolResult, ToolCategory
from emdash.planning.feature_expander import FeatureExpander
from emdash.planning.agent_api import AgentAPI


class ExpandNodeTool(BaseTool):
    """Expand from a node to get its full context."""

    name = "expand_node"
    description = (
        "Expand from a starting node to get its complete context including call graph, "
        "inheritance, and related entities. Use this after finding a relevant entity "
        "to understand its full context."
    )
    category = ToolCategory.TRAVERSAL

    def __init__(self, connection=None):
        super().__init__(connection)
        self.expander = FeatureExpander(self.connection)

    def execute(
        self,
        node_type: str,
        identifier: str,
        max_hops: int = 2,
    ) -> ToolResult:
        """Expand from a node.

        Args:
            node_type: Type of node (Function, Class, File)
            identifier: Qualified name or file path
            max_hops: Maximum relationship depth

        Returns:
            ToolResult with expanded graph
        """
        try:
            if node_type == "Function":
                graph = self.expander.expand_from_function(identifier, max_hops)
            elif node_type == "Class":
                graph = self.expander.expand_from_class(identifier, max_hops)
            elif node_type == "File":
                graph = self.expander.expand_from_file(identifier, max_hops)
            else:
                return ToolResult.error_result(
                    f"Unknown node type: {node_type}",
                    suggestions=["Use Function, Class, or File as node_type"],
                )

            suggestions = []
            if graph.call_graph:
                suggestions.append(
                    f"Found {len(graph.call_graph)} call relationships. "
                    "Use get_callers or get_callees for deeper analysis."
                )
            if graph.classes:
                suggestions.append(
                    f"Found {len(graph.classes)} related classes. "
                    "Use get_class_hierarchy to explore inheritance."
                )

            return ToolResult.success_result(
                data={
                    "root_node": graph.root_node,
                    "functions": graph.functions,
                    "classes": graph.classes,
                    "files": graph.files,
                    "call_graph": graph.call_graph[:50],  # Limit for context
                    "inheritance": graph.inheritance,
                    "imports": graph.imports,
                    "summary": {
                        "function_count": len(graph.functions),
                        "class_count": len(graph.classes),
                        "file_count": len(graph.files),
                        "call_count": len(graph.call_graph),
                    },
                },
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Use list_files to explore the directory structure.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "node_type": {
                    "type": "string",
                    "enum": ["Function", "Class", "File"],
                    "description": "Type of the starting node",
                },
                "identifier": {
                    "type": "string",
                    "description": "Qualified name for Function/Class, or path for File",
                },
                "max_hops": {
                    "type": "integer",
                    "default": 2,
                    "description": "Maximum relationship depth to traverse",
                },
            },
            required=["node_type", "identifier"],
        )


class GetCallersTool(BaseTool):
    """Get all functions that call a specific function."""

    name = "get_callers"
    description = (
        "Find all functions that call a specific function. Use this to understand "
        "who depends on this function and how it's used."
    )
    category = ToolCategory.TRAVERSAL

    def __init__(self, connection=None):
        super().__init__(connection)
        self.api = AgentAPI(self.connection)

    def execute(self, qualified_name: str) -> ToolResult:
        """Get callers of a function.

        Args:
            qualified_name: Function's qualified name

        Returns:
            ToolResult with calling functions
        """
        try:
            callers = self.api.get_function_callers(qualified_name)

            suggestions = []
            if callers:
                suggestions.append(
                    f"Found {len(callers)} callers. Use expand_node to explore any of them."
                )
            else:
                suggestions.append(
                    "No callers found. This might be an entry point or unused function."
                )

            return ToolResult.success_result(
                data={"callers": callers, "count": len(callers)},
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "qualified_name": {
                    "type": "string",
                    "description": "Qualified name of the function (e.g., 'module.Class.method')",
                },
            },
            required=["qualified_name"],
        )


class GetCalleesTool(BaseTool):
    """Get all functions called by a specific function."""

    name = "get_callees"
    description = (
        "Find all functions called by a specific function. Use this to understand "
        "what dependencies a function has."
    )
    category = ToolCategory.TRAVERSAL

    def __init__(self, connection=None):
        super().__init__(connection)
        self.api = AgentAPI(self.connection)

    def execute(self, qualified_name: str) -> ToolResult:
        """Get callees of a function.

        Args:
            qualified_name: Function's qualified name

        Returns:
            ToolResult with called functions
        """
        try:
            callees = self.api.get_function_callees(qualified_name)

            suggestions = []
            if callees:
                suggestions.append(
                    f"This function calls {len(callees)} other functions."
                )
            else:
                suggestions.append(
                    "No callees found. This is a leaf function with no dependencies."
                )

            return ToolResult.success_result(
                data={"callees": callees, "count": len(callees)},
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "qualified_name": {
                    "type": "string",
                    "description": "Qualified name of the function",
                },
            },
            required=["qualified_name"],
        )


class GetClassHierarchyTool(BaseTool):
    """Get inheritance hierarchy for a class."""

    name = "get_class_hierarchy"
    description = (
        "Get the inheritance tree for a class, including parent classes and child classes. "
        "Use this to understand class relationships and polymorphism."
    )
    category = ToolCategory.TRAVERSAL

    def __init__(self, connection=None):
        super().__init__(connection)
        self.api = AgentAPI(self.connection)

    def execute(self, class_name: str) -> ToolResult:
        """Get class hierarchy.

        Args:
            class_name: Class name or qualified name

        Returns:
            ToolResult with parents and children
        """
        try:
            result = self.api.get_class_hierarchy(class_name)

            suggestions = []
            parents = result.get("parents", [])
            children = result.get("children", [])

            if parents:
                suggestions.append(
                    f"Inherits from {len(parents)} parent class(es). "
                    "Check parent classes for shared functionality."
                )
            if children:
                suggestions.append(
                    f"Has {len(children)} child class(es). "
                    "These override or extend this class."
                )

            return ToolResult.success_result(
                data=result,
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "class_name": {
                    "type": "string",
                    "description": "Class name or qualified name",
                },
            },
            required=["class_name"],
        )


class GetFileDependenciesTool(BaseTool):
    """Get imports and files that depend on a file."""

    name = "get_file_dependencies"
    description = (
        "Get the import relationships for a file - what it imports and what imports it. "
        "Use this to understand module dependencies."
    )
    category = ToolCategory.TRAVERSAL

    def __init__(self, connection=None):
        super().__init__(connection)
        self.api = AgentAPI(self.connection)

    def execute(self, file_path: str) -> ToolResult:
        """Get file dependencies.

        Args:
            file_path: Path to the file

        Returns:
            ToolResult with imports and importers
        """
        try:
            result = self.api.get_file_dependencies(file_path)

            suggestions = []
            imports = result.get("imports", [])
            imported_by = result.get("imported_by", [])

            if imports:
                suggestions.append(
                    f"Imports {len(imports)} modules. Check these for reusable functionality."
                )
            if imported_by:
                suggestions.append(
                    f"Imported by {len(imported_by)} files. Changes here may affect them."
                )

            return ToolResult.success_result(
                data=result,
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "file_path": {
                    "type": "string",
                    "description": "Path to the file (can be partial path)",
                },
            },
            required=["file_path"],
        )


class GetImpactAnalysisTool(BaseTool):
    """Analyze potential impact of changing a file."""

    name = "get_impact_analysis"
    description = (
        "Analyze the potential impact of changing a file. Shows affected functions, "
        "dependent files, and risk level. Use this before making changes."
    )
    category = ToolCategory.TRAVERSAL

    def __init__(self, connection=None):
        super().__init__(connection)
        self.api = AgentAPI(self.connection)

    def execute(self, file_path: str) -> ToolResult:
        """Analyze impact of changes.

        Args:
            file_path: Path to the file

        Returns:
            ToolResult with impact analysis
        """
        try:
            result = self.api.get_impact_analysis(file_path)

            suggestions = []
            risk = result.get("risk_level", "unknown")

            if risk == "high":
                suggestions.append(
                    "High risk: This file has many dependents. Test thoroughly!"
                )
            elif risk == "medium":
                suggestions.append(
                    "Medium risk: Some dependents will be affected."
                )
            else:
                suggestions.append(
                    "Low risk: Limited impact expected."
                )

            total_callers = result.get("total_callers", 0)
            if total_callers > 0:
                suggestions.append(
                    f"Functions in this file are called {total_callers} times."
                )

            return ToolResult.success_result(
                data=result,
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "file_path": {
                    "type": "string",
                    "description": "Path to the file to analyze",
                },
            },
            required=["file_path"],
        )


class GetNeighborsTool(BaseTool):
    """Get immediate neighbors of any node."""

    name = "get_neighbors"
    description = (
        "Get immediate neighbors of a node via specific relationship types. "
        "Use this for flexible graph exploration."
    )
    category = ToolCategory.TRAVERSAL

    def execute(
        self,
        node_type: str,
        identifier: str,
        relationship_types: list[str] = None,
        direction: str = "both",
        limit: int = 50,
    ) -> ToolResult:
        """Get node neighbors.

        Args:
            node_type: Type of node (Function, Class, File)
            identifier: Qualified name or path
            relationship_types: Types to traverse (CALLS, INHERITS_FROM, etc.)
            direction: Direction (in, out, both)
            limit: Maximum results

        Returns:
            ToolResult with neighboring nodes
        """
        try:
            if relationship_types is None:
                relationship_types = ["CALLS", "INHERITS_FROM", "CONTAINS_FUNCTION", "CONTAINS_CLASS", "HAS_METHOD"]

            neighbors = []
            seen = set()

            # Build identifier match based on node type
            if node_type == "File":
                id_match = "(n.path = $identifier OR n.path ENDS WITH $identifier)"
            else:
                id_match = "(n.qualified_name = $identifier OR n.name = $identifier)"

            # Query each relationship type separately (Kuzu doesn't support | in rel types)
            for rel_type in relationship_types:
                if len(neighbors) >= limit:
                    break

                remaining = limit - len(neighbors)

                # Build queries based on direction
                queries = []
                if direction in ("out", "both"):
                    queries.append((f"""
                        MATCH (n:{node_type})-[:{rel_type}]->(neighbor)
                        WHERE {id_match}
                        RETURN DISTINCT
                            label(neighbor) as type,
                            neighbor.name as name,
                            COALESCE(neighbor.qualified_name, neighbor.path) as qualified_name,
                            COALESCE(neighbor.file_path, neighbor.path) as file_path
                        LIMIT $limit
                    """, rel_type))

                if direction in ("in", "both"):
                    queries.append((f"""
                        MATCH (neighbor)-[:{rel_type}]->(n:{node_type})
                        WHERE {id_match}
                        RETURN DISTINCT
                            label(neighbor) as type,
                            neighbor.name as name,
                            COALESCE(neighbor.qualified_name, neighbor.path) as qualified_name,
                            COALESCE(neighbor.file_path, neighbor.path) as file_path
                        LIMIT $limit
                    """, rel_type))

                for query, rtype in queries:
                    if len(neighbors) >= limit:
                        break
                    try:
                        results = self.connection.execute(
                            query,
                            {"identifier": identifier, "limit": remaining},
                        )
                        for r in results:
                            # Deduplicate by qualified_name
                            key = r.get("qualified_name") or r.get("name")
                            if key and key not in seen:
                                seen.add(key)
                                r["relationship"] = rtype
                                neighbors.append(r)
                                if len(neighbors) >= limit:
                                    break
                    except Exception:
                        # Skip relationship types that don't exist
                        continue

            suggestions = []
            if neighbors:
                by_type = {}
                for n in neighbors:
                    t = n.get("type", "Unknown")
                    by_type[t] = by_type.get(t, 0) + 1

                type_summary = ", ".join(f"{v} {k}s" for k, v in by_type.items())
                suggestions.append(f"Found: {type_summary}")
            else:
                suggestions.append(
                    "No neighbors found. Try different relationship types or check the identifier."
                )

            return ToolResult.success_result(
                data={
                    "node_type": node_type,
                    "identifier": identifier,
                    "neighbors": neighbors,
                    "count": len(neighbors),
                },
                suggestions=suggestions,
            )

        except Exception as e:
            error_msg = str(e)
            if "does not exist" in error_msg.lower():
                return ToolResult.error_result(
                    "Code graph not available - codebase not indexed yet.",
                    suggestions=[
                        "Use grep to search for patterns in the codebase.",
                        "Use read_file to read specific files.",
                        "Run 'emdash ingest <path>' to index the codebase first.",
                    ],
                )
            return ToolResult.error_result(error_msg)

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "node_type": {
                    "type": "string",
                    "enum": ["Function", "Class", "File"],
                    "description": "Type of the starting node",
                },
                "identifier": {
                    "type": "string",
                    "description": "Qualified name, path, or name of the node",
                },
                "relationship_types": {
                    "type": "array",
                    "items": {
                        "type": "string",
                        "enum": ["CALLS", "INHERITS_FROM", "CONTAINS_FUNCTION", "CONTAINS_CLASS", "HAS_METHOD", "IMPORTS"],
                    },
                    "description": "Types of relationships to traverse",
                },
                "direction": {
                    "type": "string",
                    "enum": ["in", "out", "both"],
                    "default": "both",
                    "description": "Direction to traverse",
                },
                "limit": {
                    "type": "integer",
                    "default": 50,
                    "description": "Maximum number of neighbors",
                },
            },
            required=["node_type", "identifier"],
        )
