"""Search tools for finding code entities and PRs."""

import os
import re
import subprocess
from pathlib import Path
from typing import Optional

from emdash.agent.tools.base import BaseTool, ToolResult, ToolCategory
from emdash.planning.similarity import SimilaritySearch


class SemanticSearchTool(BaseTool):
    """Search for code entities by semantic similarity."""

    name = "semantic_search"
    description = (
        "Search for code entities (functions, classes) semantically similar to a "
        "natural language query. Use importance_ranking=true to find code that is "
        "both relevant AND actively maintained/important to the team."
    )
    category = ToolCategory.SEARCH

    def __init__(self, connection=None):
        super().__init__(connection)
        self.similarity = SimilaritySearch(self.connection)

    def execute(
        self,
        query: str,
        entity_types: list[str] = None,
        limit: int = 10,
        min_score: float = 0.5,
        importance_ranking: bool = True,
    ) -> ToolResult:
        """Execute semantic search.

        Args:
            query: Natural language description of the code
            entity_types: Types to search (Function, Class)
            limit: Maximum results
            min_score: Minimum similarity score (0-1)
            importance_ranking: If true, re-rank by combined score
                               (semantic + file importance + PageRank)

        Returns:
            ToolResult with matching entities
        """
        try:
            if entity_types is None:
                entity_types = ["Function", "Class"]

            if importance_ranking:
                results = self.similarity.importance_weighted_search(
                    query=query,
                    entity_types=entity_types,
                    limit=limit,
                    min_score=min_score,
                )
            else:
                results = self.similarity.find_similar_code(
                    query=query,
                    entity_types=entity_types,
                    limit=limit,
                    min_score=min_score,
                )

            # Generate suggestions based on results
            suggestions = []
            if results:
                top = results[0]
                suggestions.append(
                    f"Use expand_node with node_type='{top['type']}' and "
                    f"identifier='{top['qualified_name']}' to explore further"
                )
                if importance_ranking and top.get("combined_score"):
                    suggestions.append(
                        f"Top result: {top['name']} "
                        f"(combined={top.get('combined_score', 0):.2f}, "
                        f"semantic={top.get('norm_semantic', 0):.2f}, "
                        f"importance={top.get('norm_importance', 0):.2f})"
                    )
                if top.get("file_path"):
                    suggestions.append(
                        f"Use get_file_dependencies with file_path='{top['file_path']}' "
                        "to understand module relationships"
                    )
            else:
                suggestions.append(
                    "Try a different search query or use text_search for exact matches"
                )

            return ToolResult.success_result(
                data={
                    "query": query,
                    "results": results,
                    "count": len(results),
                    "importance_ranking": importance_ranking,
                },
                suggestions=suggestions,
                metadata={"entity_types": entity_types, "min_score": min_score},
            )

        except Exception as e:
            error_msg = str(e)
            # Check if this is a missing table error (database not indexed)
            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,
                suggestions=["Check if embeddings are indexed (emdash embed index)"],
            )

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "query": {
                    "type": "string",
                    "description": "Natural language description of the code you're looking for",
                },
                "entity_types": {
                    "type": "array",
                    "items": {"type": "string", "enum": ["Function", "Class"]},
                    "default": ["Function", "Class"],
                    "description": "Types of entities to search",
                },
                "limit": {
                    "type": "integer",
                    "default": 10,
                    "description": "Maximum number of results",
                },
                "min_score": {
                    "type": "number",
                    "default": 0.5,
                    "description": "Minimum similarity score (0-1)",
                },
                "importance_ranking": {
                    "type": "boolean",
                    "default": True,
                    "description": "Re-rank results by importance (semantic + activity + PageRank)",
                },
            },
            required=["query"],
        )


class TextSearchTool(BaseTool):
    """Full-text search in code names and docstrings."""

    name = "text_search"
    description = (
        "Search for code entities by exact text match in names or docstrings. "
        "Use this when you know a specific name or keyword."
    )
    category = ToolCategory.SEARCH

    def execute(
        self,
        query: str,
        entity_types: list[str] = None,
        limit: int = 20,
    ) -> ToolResult:
        """Execute text search.

        Args:
            query: Text to search for
            entity_types: Types to search (Function, Class)
            limit: Maximum results

        Returns:
            ToolResult with matching entities
        """
        try:
            if entity_types is None:
                entity_types = ["Function", "Class"]

            results = []

            if "Function" in entity_types:
                func_results = self.connection.execute(
                    """
                    MATCH (f:Function)
                    WHERE lower(f.name) CONTAINS lower($search_text)
                       OR lower(f.docstring) CONTAINS lower($search_text)
                    RETURN 'Function' AS type,
                           f.name AS name,
                           f.qualified_name AS qualified_name,
                           f.file_path AS file_path,
                           f.docstring AS docstring
                    LIMIT $limit
                    """,
                    {"search_text": query, "limit": limit},
                )
                results.extend(func_results)

            if "Class" in entity_types and len(results) < limit:
                remaining = limit - len(results)
                class_results = self.connection.execute(
                    """
                    MATCH (c:Class)
                    WHERE lower(c.name) CONTAINS lower($search_text)
                       OR lower(c.docstring) CONTAINS lower($search_text)
                    RETURN 'Class' AS type,
                           c.name AS name,
                           c.qualified_name AS qualified_name,
                           c.file_path AS file_path,
                           c.docstring AS docstring
                    LIMIT $limit
                    """,
                    {"search_text": query, "limit": remaining},
                )
                results.extend(class_results)

            suggestions = []
            if results:
                suggestions.append(
                    f"Found {len(results)} matches. Use expand_node to explore any result."
                )
            else:
                suggestions.append(
                    "No exact matches found. Try semantic_search for fuzzy matching."
                )

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

        except Exception as e:
            error_msg = str(e)
            # Check if this is a missing table error (database not indexed)
            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={
                "query": {
                    "type": "string",
                    "description": "Text to search for in names or docstrings",
                },
                "entity_types": {
                    "type": "array",
                    "items": {"type": "string", "enum": ["Function", "Class"]},
                    "default": ["Function", "Class"],
                    "description": "Types of entities to search",
                },
                "limit": {
                    "type": "integer",
                    "default": 20,
                    "description": "Maximum number of results",
                },
            },
            required=["query"],
        )


class GrepTool(BaseTool):
    """Search for patterns in files using grep/ripgrep."""

    name = "grep"
    description = (
        "Search for text patterns in files within the repository. "
        "Uses ripgrep if available, otherwise falls back to grep. "
        "Useful for finding specific code patterns, strings, or identifiers."
    )
    category = ToolCategory.SEARCH

    def __init__(self, connection=None, repo_root: str = None):
        super().__init__(connection)
        self.repo_root = Path(repo_root) if repo_root else Path.cwd()
        self._has_rg = self._check_ripgrep()

    def _check_ripgrep(self) -> bool:
        """Check if ripgrep is available."""
        try:
            subprocess.run(["rg", "--version"], capture_output=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False

    def execute(
        self,
        pattern: str,
        glob: str = None,
        file_type: str = None,
        case_sensitive: bool = True,
        context_lines: int = 0,
        max_results: int = 50,
        regex: bool = True,
    ) -> ToolResult:
        """Execute grep search.

        Args:
            pattern: Search pattern (regex by default)
            glob: File glob pattern (e.g., "*.py", "src/**/*.ts")
            file_type: File type filter (e.g., "py", "js", "ts")
            case_sensitive: Case-sensitive search (default True)
            context_lines: Lines of context around matches
            max_results: Maximum number of matches to return
            regex: Treat pattern as regex (default True)

        Returns:
            ToolResult with matching lines and file locations
        """
        try:
            if self._has_rg:
                results = self._search_ripgrep(
                    pattern, glob, file_type, case_sensitive,
                    context_lines, max_results, regex
                )
            else:
                results = self._search_grep(
                    pattern, glob, case_sensitive,
                    context_lines, max_results, regex
                )

            suggestions = []
            if results:
                files = set(r["file"] for r in results)
                suggestions.append(
                    f"Found {len(results)} matches in {len(files)} files"
                )
                if len(results) == max_results:
                    suggestions.append(
                        "Results may be truncated. Use max_results to increase limit."
                    )
                suggestions.append(
                    "Use semantic_search for finding conceptually similar code"
                )
            else:
                suggestions.append(
                    "No matches found. Try a different pattern or use semantic_search."
                )

            return ToolResult.success_result(
                data={
                    "pattern": pattern,
                    "matches": results,
                    "count": len(results),
                    "tool": "ripgrep" if self._has_rg else "grep",
                },
                suggestions=suggestions,
                metadata={
                    "glob": glob,
                    "file_type": file_type,
                    "case_sensitive": case_sensitive,
                },
            )

        except Exception as e:
            return ToolResult.error_result(
                str(e),
                suggestions=["Check if the pattern is valid regex"],
            )

    def _search_ripgrep(
        self,
        pattern: str,
        glob: str,
        file_type: str,
        case_sensitive: bool,
        context_lines: int,
        max_results: int,
        regex: bool,
    ) -> list[dict]:
        """Search using ripgrep."""
        cmd = ["rg", "--json"]

        if not case_sensitive:
            cmd.append("-i")
        if not regex:
            cmd.append("-F")
        if context_lines > 0:
            cmd.extend(["-C", str(context_lines)])
        if glob:
            cmd.extend(["--glob", glob])
        if file_type:
            cmd.extend(["-t", file_type])

        cmd.extend(["--max-count", str(max_results)])
        cmd.append(pattern)
        cmd.append(str(self.repo_root))

        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            cwd=str(self.repo_root),
        )

        matches = []
        import json
        for line in result.stdout.strip().split("\n"):
            if not line:
                continue
            try:
                data = json.loads(line)
                if data.get("type") == "match":
                    match_data = data.get("data", {})
                    path = match_data.get("path", {}).get("text", "")
                    line_number = match_data.get("line_number", 0)
                    lines = match_data.get("lines", {}).get("text", "").rstrip()
                    matches.append({
                        "file": path,
                        "line": line_number,
                        "content": lines,
                    })
            except json.JSONDecodeError:
                continue

        return matches[:max_results]

    def _search_grep(
        self,
        pattern: str,
        glob: str,
        case_sensitive: bool,
        context_lines: int,
        max_results: int,
        regex: bool,
    ) -> list[dict]:
        """Search using grep (fallback)."""
        cmd = ["grep", "-rn"]

        if not case_sensitive:
            cmd.append("-i")
        if not regex:
            cmd.append("-F")
        if context_lines > 0:
            cmd.extend(["-C", str(context_lines)])
        if glob:
            cmd.extend(["--include", glob])

        cmd.append(pattern)
        cmd.append(str(self.repo_root))

        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            cwd=str(self.repo_root),
        )

        matches = []
        for line in result.stdout.strip().split("\n"):
            if not line:
                continue
            match = re.match(r"^(.+?):(\d+):(.*)$", line)
            if match:
                matches.append({
                    "file": match.group(1),
                    "line": int(match.group(2)),
                    "content": match.group(3),
                })

        return matches[:max_results]

    def get_schema(self) -> dict:
        return self._make_schema(
            properties={
                "pattern": {
                    "type": "string",
                    "description": "Search pattern (regex by default)",
                },
                "glob": {
                    "type": "string",
                    "description": "File glob pattern (e.g., '*.py', 'src/**/*.ts')",
                },
                "file_type": {
                    "type": "string",
                    "description": "File type filter (e.g., 'py', 'js', 'ts')",
                },
                "case_sensitive": {
                    "type": "boolean",
                    "default": True,
                    "description": "Case-sensitive search",
                },
                "context_lines": {
                    "type": "integer",
                    "default": 0,
                    "description": "Lines of context around matches",
                },
                "max_results": {
                    "type": "integer",
                    "default": 50,
                    "description": "Maximum number of matches to return",
                },
                "regex": {
                    "type": "boolean",
                    "default": True,
                    "description": "Treat pattern as regex (False for literal string)",
                },
            },
            required=["pattern"],
        )
