import os
import time
from pathlib import Path
from typing import Dict, Any, List

from clawagents.tools.registry import Tool, ToolResult

ROOT = Path.cwd()

def safe_path(p: str) -> Path:
    resolved = (ROOT / p).resolve()
    if resolved != ROOT and not str(resolved).startswith(str(ROOT) + os.sep):
        raise ValueError(f"Path traversal blocked: {p}")
    return resolved


class LsTool:
    name = "ls"
    description = "List files and directories with metadata (size, modified time)."
    parameters = {
        "path": {"type": "string", "description": "Path to list. Default: current directory", "required": True}
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        target_path = safe_path(str(args.get("path", ".")))
        try:
            if not target_path.exists() or not target_path.is_dir():
                return ToolResult(success=False, output="", error=f"ls failed: Not a directory or does not exist at {target_path}")

            lines = []
            for entry in sorted(target_path.iterdir(), key=lambda e: (not e.is_dir(), e.name.lower())):
                try:
                    stat = entry.stat()
                    size = stat.st_size
                    mtime = time.strftime("%Y-%m-%d %H:%M", time.localtime(stat.st_mtime))

                    if entry.is_dir():
                        lines.append(f"[DIR]  {entry.name}/")
                    else:
                        size_str = _format_size(size)
                        lines.append(f"[FILE] {entry.name} ({size_str}, {mtime})")
                except OSError:
                    lines.append(f"[????] {entry.name}")

            return ToolResult(success=True, output="\n".join(lines) or "(empty directory)")
        except Exception as e:
            return ToolResult(success=False, output="", error=f"ls failed: {str(e)}")


class ReadFileTool:
    name = "read_file"
    description = "Read file contents with line numbers. Supports offset/limit for pagination."
    parameters = {
        "path": {"type": "string", "description": "Path to the file to read", "required": True},
        "offset": {"type": "number", "description": "Line number to start from (0-indexed). Default: 0"},
        "limit": {"type": "number", "description": "Max lines to return. Default: 100"}
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        file_path = safe_path(str(args.get("path", "")))
        offset = int(args.get("offset", 0))
        limit = int(args.get("limit", 100))

        try:
            content = file_path.read_text("utf-8")
            lines = content.splitlines()
            slice_lines = lines[offset:offset + limit]
            
            numbered = [f"{str(offset + i + 1).rjust(4)}: {line}" for i, line in enumerate(slice_lines)]
            header = f"File: {file_path} ({len(lines)} lines total, showing {offset + 1}-{offset + len(slice_lines)})"
            
            return ToolResult(success=True, output=header + "\n" + "\n".join(numbered))
        except Exception as e:
            return ToolResult(success=False, output="", error=f"read_file failed: {str(e)}")


class WriteFileTool:
    name = "write_file"
    description = "Write content to a file. Creates parent directories if needed."
    parameters = {
        "path": {"type": "string", "description": "Path to write the file", "required": True},
        "content": {"type": "string", "description": "Content to write to the file", "required": True}
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        file_path = safe_path(str(args.get("path", "")))
        content = str(args.get("content", ""))

        try:
            file_path.parent.mkdir(parents=True, exist_ok=True)
            file_path.write_text(content, "utf-8")
            return ToolResult(success=True, output=f"Wrote {len(content)} bytes to {file_path}")
        except Exception as e:
            return ToolResult(success=False, output="", error=f"write_file failed: {str(e)}")


class GrepTool:
    name = "grep"
    description = "Search for a text pattern in files. Supports recursive multi-file search with glob filtering."
    parameters = {
        "path": {"type": "string", "description": "File or directory to search", "required": True},
        "pattern": {"type": "string", "description": "Text pattern to search for", "required": True},
        "glob_filter": {"type": "string", "description": "Glob pattern to filter files (e.g., '*.py'). Only used when path is a directory."},
        "recursive": {"type": "boolean", "description": "Search recursively in subdirectories. Default: false"}
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        target = safe_path(str(args.get("path", "")))
        pattern = str(args.get("pattern", ""))
        glob_filter = str(args.get("glob_filter", "*"))
        recursive = bool(args.get("recursive", False))

        if not pattern:
            return ToolResult(success=False, output="", error="No pattern provided")

        try:
            if target.is_file():
                return self._search_file(target, pattern)

            if not target.is_dir():
                return ToolResult(success=False, output="", error=f"Path does not exist: {target}")

            # Multi-file search
            glob_pattern = f"**/{glob_filter}" if recursive else glob_filter
            all_matches: list[str] = []
            files_searched = 0
            max_matches = 100

            for file_path in sorted(target.glob(glob_pattern)):
                if not file_path.is_file():
                    continue
                files_searched += 1
                try:
                    content = file_path.read_text("utf-8")
                except (UnicodeDecodeError, OSError):
                    continue

                rel = file_path.relative_to(target)
                for i, line in enumerate(content.splitlines()):
                    if pattern in line:
                        all_matches.append(f"{rel}:{i + 1}: {line.strip()}")
                        if len(all_matches) >= max_matches:
                            break
                if len(all_matches) >= max_matches:
                    break

            if not all_matches:
                return ToolResult(success=True, output=f"No matches for \"{pattern}\" in {files_searched} files under {target}")

            truncated = f" (truncated at {max_matches})" if len(all_matches) >= max_matches else ""
            output = "\n".join(all_matches)
            return ToolResult(success=True, output=f"{len(all_matches)} match(es) in {files_searched} files{truncated}:\n{output}")

        except Exception as e:
            return ToolResult(success=False, output="", error=f"grep failed: {str(e)}")

    def _search_file(self, file_path: Path, pattern: str) -> ToolResult:
        try:
            content = file_path.read_text("utf-8")
            lines = content.splitlines()
            matches = [{"line": line, "num": i + 1} for i, line in enumerate(lines) if pattern in line]

            if not matches:
                return ToolResult(success=True, output=f"No matches for \"{pattern}\" in {file_path}")

            output = "\n".join([f"{str(m['num']).rjust(4)}: {m['line']}" for m in matches])
            return ToolResult(success=True, output=f"{len(matches)} match(es) in {file_path}:\n{output}")
        except Exception as e:
            return ToolResult(success=False, output="", error=f"grep failed: {str(e)}")


class EditFileTool:
    name = "edit_file"
    description = "Edit a file by replacing a specific block of text. The target must exactly match existing content."
    parameters = {
        "path": {"type": "string", "description": "Path to the file to edit", "required": True},
        "target": {"type": "string", "description": "The exact block of text to replace", "required": True},
        "replacement": {"type": "string", "description": "The new text", "required": True},
        "replace_all": {"type": "boolean", "description": "Replace all occurrences (default: false, requires unique match)"}
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        file_path = safe_path(str(args.get("path", "")))
        target = str(args.get("target", ""))
        replacement = str(args.get("replacement", ""))
        replace_all = bool(args.get("replace_all", False))

        try:
            if not file_path.exists():
                return ToolResult(success=False, output="", error=f"edit_file failed: File does not exist at {file_path}")

            content = file_path.read_text("utf-8")

            if target not in content:
                return ToolResult(success=False, output="", error=f"edit_file failed: Could not find exact target text in {file_path}. Check whitespace and line endings.")

            count = content.count(target)
            if count > 1 and not replace_all:
                return ToolResult(success=False, output="", error=f"edit_file failed: Target text appears {count} times. Use replace_all=true or provide a more specific target.")

            if replace_all:
                new_content = content.replace(target, replacement)
            else:
                new_content = content.replace(target, replacement, 1)

            file_path.write_text(new_content, "utf-8")

            return ToolResult(
                success=True,
                output=f"Edited {file_path}: replaced {count if replace_all else 1} occurrence(s) ({len(target)} → {len(replacement)} bytes)"
            )
        except Exception as e:
            return ToolResult(success=False, output="", error=f"edit_file failed: {str(e)}")


class GlobTool:
    name = "glob"
    description = "Find files matching a glob pattern. Use '**/*.py' for recursive search."
    parameters = {
        "pattern": {"type": "string", "description": "Glob pattern (e.g., '**/*.py', 'src/**/*.ts')", "required": True},
        "path": {"type": "string", "description": "Root directory to search from. Default: current directory"}
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        root = safe_path(str(args.get("path", ".")))
        pattern = str(args.get("pattern", ""))

        if not pattern:
            return ToolResult(success=False, output="", error="No glob pattern provided")

        try:
            if not root.exists() or not root.is_dir():
                return ToolResult(success=False, output="", error=f"Directory does not exist: {root}")

            matches = sorted(root.glob(pattern))
            # Filter to files only, limit results
            max_results = 200
            results: list[str] = []
            for m in matches:
                if m.is_file():
                    try:
                        rel = m.relative_to(root)
                        size_str = _format_size(m.stat().st_size)
                        results.append(f"{rel} ({size_str})")
                    except (OSError, ValueError):
                        results.append(str(m))
                if len(results) >= max_results:
                    break

            if not results:
                return ToolResult(success=True, output=f"No files matching '{pattern}' in {root}")

            truncated = f" (showing first {max_results})" if len(results) >= max_results else ""
            return ToolResult(success=True, output=f"{len(results)} file(s) matching '{pattern}'{truncated}:\n" + "\n".join(results))

        except Exception as e:
            return ToolResult(success=False, output="", error=f"glob failed: {str(e)}")


def _format_size(size: int) -> str:
    if size < 1024:
        return f"{size} B"
    elif size < 1024 * 1024:
        return f"{size / 1024:.1f} KB"
    else:
        return f"{size / (1024 * 1024):.1f} MB"


filesystem_tools: List[Tool] = [
    LsTool(),
    ReadFileTool(),
    WriteFileTool(),
    EditFileTool(),
    GrepTool(),
    GlobTool(),
]
