"""
File system tools for GSD-RLM agents.

All tools are scoped to a project directory for safety.
Tools extend RLM-Toolkit's Tool base class.
"""

from pathlib import Path
from typing import Optional, List, Union
import os
import re

from rlm_toolkit.tools import Tool


class ReadFileTool(Tool):
    """Read file contents (INT-09)."""

    name = "read_file"
    description = "Read the contents of a file. Returns file content as string."

    def __init__(
        self,
        project_dir: Path,
        max_size: int = 50000,  # 50KB default max
    ):
        self.project_dir = Path(project_dir).resolve()
        self.max_size = max_size

    def run(self, path: str) -> str:
        """Read file contents.

        Args:
            path: Relative or absolute path to file

        Returns:
            File contents as string
        """
        # Resolve path and check it's within project
        try:
            file_path = self._resolve_path(path)
        except PermissionError as e:
            return f"Error: {e}"

        if not file_path.exists():
            return f"Error: File not found: {path}"

        if not file_path.is_file():
            return f"Error: Not a file: {path}"

        # Check file size
        size = file_path.stat().st_size
        if size > self.max_size:
            return f"Error: File too large ({size} bytes). Max: {self.max_size}"

        try:
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()

            # Add line numbers for context (like GSD Read tool)
            lines = content.splitlines(keepends=True)
            numbered = [f"{i + 1}: {line}" for i, line in enumerate(lines)]

            return "".join(numbered) if numbered else ""

        except UnicodeDecodeError:
            return f"Error: Binary file or unsupported encoding: {path}"
        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error: {e}"

    def _resolve_path(self, path: str) -> Path:
        """Resolve path and ensure it's within project directory."""
        path = Path(path)

        if path.is_absolute():
            resolved = path.resolve()
        else:
            resolved = (self.project_dir / path).resolve()

        # Security check: path must be within project
        try:
            resolved.relative_to(self.project_dir)
        except ValueError:
            raise PermissionError(f"Path outside project directory: {path}")

        return resolved


class WriteFileTool(Tool):
    """Write content to files (INT-10)."""

    name = "write_file"
    description = "Write content to a file. Creates directories if needed."

    def __init__(self, project_dir: Path):
        self.project_dir = Path(project_dir).resolve()

    def run(self, input: str) -> str:
        """Write content to file.

        Args:
            input: Format "path|||content" or JSON with path and content

        Returns:
            Success message or error
        """
        try:
            path, content = self._parse_input(input)
        except ValueError as e:
            return f"Error: {e}"

        # Resolve path
        try:
            file_path = self._resolve_path(path)
        except PermissionError as e:
            return f"Error: {e}"

        # Create parent directories
        file_path.parent.mkdir(parents=True, exist_ok=True)

        try:
            with open(file_path, "w", encoding="utf-8") as f:
                f.write(content)

            return f"Successfully wrote {len(content)} characters to {path}"

        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error: {e}"

    def _parse_input(self, input: str) -> tuple:
        """Parse input into path and content."""
        # Try pipe-separated format first
        if "|||" in input:
            path, content = input.split("|||", 1)
            return path.strip(), content

        # Try JSON format
        try:
            import json

            data = json.loads(input)
            return data["path"], data["content"]
        except (json.JSONDecodeError, KeyError):
            pass

        raise ValueError("Invalid format. Use: path|||content or JSON {path, content}")

    def _resolve_path(self, path: str) -> Path:
        """Resolve path and ensure it's within project directory."""
        path = Path(path)

        if path.is_absolute():
            resolved = path.resolve()
        else:
            resolved = (self.project_dir / path).resolve()

        # Security check
        try:
            resolved.relative_to(self.project_dir)
        except ValueError:
            raise PermissionError(f"Path outside project directory: {path}")

        return resolved


class EditFileTool(Tool):
    """Edit specific portions of existing files (INT-10)."""

    name = "edit_file"
    description = (
        "Edit a file by replacing specific text. "
        "Use when you need to modify part of a file without rewriting it entirely."
    )

    def __init__(self, project_dir: Path):
        self.project_dir = Path(project_dir).resolve()

    def run(self, input: str) -> str:
        """Edit file by replacing text.

        Args:
            input: Format "path|||old_string|||new_string" or JSON

        Returns:
            Success message or error
        """
        try:
            path, old_string, new_string = self._parse_input(input)
        except ValueError as e:
            return f"Error: {e}"

        # Resolve path
        try:
            file_path = self._resolve_path(path)
        except PermissionError as e:
            return f"Error: {e}"

        if not file_path.exists():
            return f"Error: File not found: {path}"

        try:
            # Read current content
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()

            # Check for exact match
            if old_string not in content:
                return (
                    f"Error: old_string not found in file. "
                    f"Make sure to match the exact text including whitespace."
                )

            # Check for multiple matches
            matches = content.count(old_string)
            if matches > 1:
                return (
                    f"Error: old_string found {matches} times in file. "
                    f"Provide more context to make it unique."
                )

            # Perform replacement
            new_content = content.replace(old_string, new_string, 1)

            # Write back
            with open(file_path, "w", encoding="utf-8") as f:
                f.write(new_content)

            return f"Successfully edited {path}"

        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error: {e}"

    def _parse_input(self, input: str) -> tuple:
        """Parse input into path, old_string, new_string."""
        # Try pipe-separated format
        parts = input.split("|||")
        if len(parts) >= 3:
            return parts[0].strip(), parts[1], "|||".join(parts[2:])

        # Try JSON format
        try:
            import json

            data = json.loads(input)
            return (data["path"], data["old_string"], data["new_string"])
        except (json.JSONDecodeError, KeyError):
            pass

        raise ValueError(
            "Invalid format. Use: path|||old_string|||new_string "
            "or JSON {path, old_string, new_string}"
        )

    def _resolve_path(self, path: str) -> Path:
        """Resolve path and ensure it's within project directory."""
        path = Path(path)

        if path.is_absolute():
            resolved = path.resolve()
        else:
            resolved = (self.project_dir / path).resolve()

        try:
            resolved.relative_to(self.project_dir)
        except ValueError:
            raise PermissionError(f"Path outside project directory: {path}")

        return resolved


class GlobTool(Tool):
    """Search files by glob pattern (INT-11)."""

    name = "glob"
    description = (
        "Find files matching a glob pattern. "
        "Examples: '**/*.py' for all Python files, 'src/**/*.ts' for TypeScript in src."
    )

    def __init__(
        self,
        project_dir: Path,
        max_results: int = 1000,
    ):
        self.project_dir = Path(project_dir).resolve()
        self.max_results = max_results

    def run(self, pattern: str) -> str:
        """Find files matching pattern.

        Args:
            pattern: Glob pattern (e.g., "**/*.py")

        Returns:
            List of matching file paths, one per line
        """
        try:
            matches = list(self.project_dir.glob(pattern))

            # Sort by modification time (newest first)
            matches.sort(key=lambda p: p.stat().st_mtime, reverse=True)

            # Limit results
            matches = matches[: self.max_results]

            if not matches:
                return f"No files found matching pattern: {pattern}"

            # Format as relative paths
            results = []
            for match in matches:
                try:
                    rel_path = match.relative_to(self.project_dir)
                    results.append(str(rel_path))
                except ValueError:
                    results.append(str(match))

            return "\n".join(results)

        except Exception as e:
            return f"Error: {e}"


class GrepTool(Tool):
    """Search file contents by regex pattern (INT-11)."""

    name = "grep"
    description = (
        "Search for pattern in file contents using regex. "
        "Returns file paths and line numbers with matches."
    )

    def __init__(
        self,
        project_dir: Path,
        include_patterns: Optional[List[str]] = None,
        max_results: int = 100,
    ):
        self.project_dir = Path(project_dir).resolve()
        self.include_patterns = include_patterns or ["*"]
        self.max_results = max_results

    def run(self, pattern: str) -> str:
        """Search for pattern in files.

        Args:
            pattern: Regex pattern to search for

        Returns:
            File paths and line numbers with matches
        """
        try:
            # Compile regex
            regex = re.compile(pattern, re.IGNORECASE)
        except re.error as e:
            return f"Error: Invalid regex pattern: {e}"

        results = []
        files_searched = 0
        matches_found = 0

        try:
            # Walk project directory
            for root, dirs, files in os.walk(self.project_dir):
                # Skip hidden directories and common ignore patterns
                dirs[:] = [
                    d
                    for d in dirs
                    if not d.startswith(".")
                    and d
                    not in ("node_modules", "__pycache__", ".git", "venv", ".venv")
                ]

                for filename in files:
                    # Check include patterns
                    if not self._should_include(filename):
                        continue

                    file_path = Path(root) / filename

                    try:
                        with open(file_path, "r", encoding="utf-8") as f:
                            for line_num, line in enumerate(f, 1):
                                if regex.search(line):
                                    rel_path = file_path.relative_to(self.project_dir)
                                    results.append(
                                        f"{rel_path}:{line_num}: {line.rstrip()}"
                                    )
                                    matches_found += 1

                                    if matches_found >= self.max_results:
                                        break

                        files_searched += 1

                        if matches_found >= self.max_results:
                            break

                    except (UnicodeDecodeError, PermissionError):
                        # Skip binary or inaccessible files
                        continue

                if matches_found >= self.max_results:
                    break

            if not results:
                return f"No matches found for pattern: {pattern}"

            header = f"Found {matches_found} matches in {files_searched} files:\n\n"
            return header + "\n".join(results)

        except Exception as e:
            return f"Error: {e}"

    def _should_include(self, filename: str) -> bool:
        """Check if filename matches include patterns."""
        from fnmatch import fnmatch

        for pattern in self.include_patterns:
            if fnmatch(filename, pattern):
                return True
        return False
