"""
GrepTool - Search file contents with regex patterns

Following OpenCode architecture, provides content search with:
- Regex pattern matching via Python re module
- Optional ripgrep (rg) fallback for performance
- File glob filtering
- Case-insensitive option
- .gitignore respect
- Type-safe implementation
"""

from pathlib import Path
from typing import Any, Optional, List, Dict
import re
import subprocess
import shutil

from groknroll.tools.base_tool import BaseTool


class GrepMatch:
    """
    Result of a grep match

    Attributes:
        file: Path to the file containing the match
        line: Line number (1-indexed)
        content: Content of the matching line
        match: The matched text
    """

    def __init__(self, file: Path, line: int, content: str, match: str):
        self.file = file
        self.line = line
        self.content = content
        self.match = match

    def to_dict(self) -> Dict[str, Any]:
        """Convert match to dictionary"""
        return {
            "file": str(self.file),
            "line": self.line,
            "content": self.content,
            "match": self.match,
        }

    def __str__(self) -> str:
        """String representation"""
        return f"{self.file}:{self.line}:{self.content}"


class GrepTool(BaseTool):
    """
    Tool for searching file contents with regex patterns

    Accepts:
        pattern: str - Regex pattern to search for
        glob: str (optional) - Glob pattern to filter files (e.g., "*.py")
        case_insensitive: bool (optional) - Case-insensitive search (default: False)
        max_results: int (optional) - Maximum number of results (default: 100)

    Returns:
        List[GrepMatch] - List of matches with file, line, content

    Raises:
        ValueError: If pattern is empty or invalid regex

    Example:
        tool = GrepTool()
        results = await tool.execute(pattern="def.*test", glob="*.py")
        for match in results:
            print(f"{match.file}:{match.line}: {match.content}")
    """

    def __init__(self, workspace_root: Optional[str] = None):
        """
        Initialize GrepTool

        Args:
            workspace_root: Optional root directory for searching.
                           Defaults to current working directory.
        """
        self._workspace_root = Path(workspace_root) if workspace_root else Path.cwd()
        self._has_ripgrep = shutil.which("rg") is not None

    @property
    def name(self) -> str:
        """Tool identifier"""
        return "grep"

    @property
    def description(self) -> str:
        """Human-readable description"""
        return "Search file contents with regex patterns"

    def validate_params(self, **kwargs) -> dict[str, Any]:
        """
        Validate parameters before execution

        Args:
            **kwargs: Must contain 'pattern' parameter

        Returns:
            Validated parameters dict

        Raises:
            ValueError: If required parameters are missing or invalid
        """
        if "pattern" not in kwargs:
            raise ValueError("pattern parameter is required")

        pattern = kwargs["pattern"]
        if not isinstance(pattern, str):
            raise ValueError(f"pattern must be a string, got {type(pattern).__name__}")

        if not pattern.strip():
            raise ValueError("pattern cannot be empty")

        # Validate pattern is valid regex
        try:
            re.compile(pattern)
        except re.error as e:
            raise ValueError(f"Invalid regex pattern: {e}")

        # Validate glob if provided
        if "glob" in kwargs and kwargs["glob"] is not None:
            glob = kwargs["glob"]
            if not isinstance(glob, str):
                raise ValueError(f"glob must be a string or None, got {type(glob).__name__}")

        # Validate case_insensitive if provided
        if "case_insensitive" in kwargs:
            case_insensitive = kwargs["case_insensitive"]
            if not isinstance(case_insensitive, bool):
                raise ValueError(f"case_insensitive must be a boolean, got {type(case_insensitive).__name__}")

        # Validate max_results if provided
        if "max_results" in kwargs:
            max_results = kwargs["max_results"]
            if not isinstance(max_results, int):
                raise ValueError(f"max_results must be an integer, got {type(max_results).__name__}")
            if max_results <= 0:
                raise ValueError(f"max_results must be positive, got {max_results}")

        return kwargs

    async def execute(self, **kwargs) -> List[GrepMatch]:
        """
        Execute grep search

        Args:
            **kwargs: Must contain 'pattern' parameter

        Returns:
            List[GrepMatch] with search results

        Raises:
            ValueError: If pattern is invalid
        """
        # Validate parameters
        validated = self.validate_params(**kwargs)
        pattern = validated["pattern"]
        glob = validated.get("glob")
        case_insensitive = validated.get("case_insensitive", False)
        max_results = validated.get("max_results", 100)

        # Try ripgrep first if available, otherwise use Python regex
        if self._has_ripgrep:
            return await self._grep_with_ripgrep(pattern, glob, case_insensitive, max_results)
        else:
            return await self._grep_with_python(pattern, glob, case_insensitive, max_results)

    async def _grep_with_ripgrep(
        self, pattern: str, glob: Optional[str], case_insensitive: bool, max_results: int
    ) -> List[GrepMatch]:
        """Use ripgrep for searching"""
        cmd = ["rg", "--line-number", "--no-heading", "--with-filename"]

        if case_insensitive:
            cmd.append("--ignore-case")

        if glob:
            cmd.extend(["--glob", glob])

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

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=30,
            )

            matches = []
            for line in result.stdout.splitlines()[:max_results]:
                # Format: file:line:content
                parts = line.split(":", 2)
                if len(parts) >= 3:
                    file_path = Path(parts[0])
                    line_num = int(parts[1])
                    content = parts[2]

                    matches.append(GrepMatch(
                        file=file_path,
                        line=line_num,
                        content=content,
                        match=pattern,
                    ))

            return matches

        except (subprocess.TimeoutExpired, subprocess.CalledProcessError, ValueError):
            # Fall back to Python regex if ripgrep fails
            return await self._grep_with_python(pattern, glob, case_insensitive, max_results)

    async def _grep_with_python(
        self, pattern: str, glob: Optional[str], case_insensitive: bool, max_results: int
    ) -> List[GrepMatch]:
        """Use Python regex for searching"""
        flags = re.IGNORECASE if case_insensitive else 0
        regex = re.compile(pattern, flags)

        matches = []

        # Determine which files to search
        if glob:
            files = list(self._workspace_root.glob(f"**/{glob}"))
        else:
            files = list(self._workspace_root.glob("**/*"))

        # Filter out directories and ignored paths
        files = [f for f in files if f.is_file() and not self._should_ignore(f)]

        # Search each file
        for file_path in files:
            if len(matches) >= max_results:
                break

            try:
                content = file_path.read_text(encoding="utf-8")

                for line_num, line in enumerate(content.splitlines(), start=1):
                    if len(matches) >= max_results:
                        break

                    match = regex.search(line)
                    if match:
                        matches.append(GrepMatch(
                            file=file_path,
                            line=line_num,
                            content=line,
                            match=match.group(0),
                        ))

            except (UnicodeDecodeError, PermissionError):
                # Skip files that can't be read
                continue

        return matches

    def _should_ignore(self, file_path: Path) -> bool:
        """Check if file should be ignored based on common patterns"""
        ignore_patterns = {
            ".git",
            "__pycache__",
            "node_modules",
            ".venv",
            "venv",
            ".env",
            ".pytest_cache",
            ".mypy_cache",
            ".ruff_cache",
            "dist",
            "build",
            "*.pyc",
            "*.pyo",
            "*.so",
            "*.dylib",
        }

        path_str = str(file_path)
        for pattern in ignore_patterns:
            if pattern in path_str:
                return True

        return False
