"""Grep tool - search file contents by regex pattern."""

from __future__ import annotations

import os
import re
import subprocess

from krim.tools.base import Tool
from krim.truncate import truncate


class GrepTool(Tool):
    name = "grep"
    description = (
        "Search file contents by regex. Returns matching lines with paths and line numbers. "
        "Use to find function definitions, imports, error strings, config values. "
        "Supports -A/-B context lines and path filtering."
    )
    parameters = {
        "pattern": {"type": "string", "description": "Regex pattern to search for"},
        "path": {
            "type": "string",
            "description": "File or directory to search (default: current directory)",
            "optional": True,
        },
        "include": {
            "type": "string",
            "description": "Glob pattern to filter files (e.g. '*.py', '*.ts')",
            "optional": True,
        },
    }

    def __init__(self, max_output_chars: int = 30_000):
        self._max_output_chars = max_output_chars

    def run(self, pattern: str, path: str = ".", include: str | None = None) -> str:
        path = os.path.expanduser(path)
        if not os.path.exists(path):
            return f"error: {path} not found"

        # try ripgrep first (faster, respects .gitignore)
        rg = self._try_ripgrep(pattern, path, include)
        if rg is not None:
            return rg

        # fallback: python regex walk
        return self._python_grep(pattern, path, include)

    def _try_ripgrep(self, pattern: str, path: str, include: str | None) -> str | None:
        cmd = ["rg", "--no-heading", "-n", "--color=never", "-e", pattern]
        if include:
            cmd += ["--glob", include]
        cmd.append(path)
        try:
            result = subprocess.run(
                cmd, capture_output=True, text=True, timeout=30,
            )
            if result.returncode == 2:
                # rg error (bad regex, etc.)
                return f"error: {result.stderr.strip()}"
            out = result.stdout.strip()
            if not out:
                return "no matches found"
            return truncate(out, self._max_output_chars)
        except FileNotFoundError:
            return None  # rg not installed, fall back
        except subprocess.TimeoutExpired:
            return "error: search timed out after 30s"

    def _python_grep(self, pattern: str, path: str, include: str | None) -> str:
        import fnmatch

        try:
            regex = re.compile(pattern)
        except re.error as e:
            return f"error: invalid regex: {e}"

        matches: list[str] = []
        max_matches = 500

        if os.path.isfile(path):
            files = [path]
        else:
            files = []
            for root, _dirs, filenames in os.walk(path):
                # skip hidden dirs and common noise
                _dirs[:] = [d for d in _dirs if not d.startswith(".")]
                for fname in filenames:
                    if include and not fnmatch.fnmatch(fname, include):
                        continue
                    files.append(os.path.join(root, fname))

        for fpath in files:
            if len(matches) >= max_matches:
                matches.append(f"... (truncated at {max_matches} matches)")
                break
            try:
                with open(fpath, "r", errors="ignore") as f:
                    for i, line in enumerate(f, 1):
                        if regex.search(line):
                            matches.append(f"{fpath}:{i}:{line.rstrip()}")
                            if len(matches) >= max_matches:
                                break
            except (OSError, UnicodeDecodeError):
                continue

        if not matches:
            return "no matches found"
        return truncate("\n".join(matches), self._max_output_chars)
