import os
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:
    return (ROOT / p).resolve()


class LsTool:
    name = "ls"
    description = "List files and directories at the given path."
    parameters = {
        "path": {"type": "string", "description": "Absolute or relative path to list", "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 target_path.iterdir():
                prefix = "[DIR]  " if entry.is_dir() else "[FILE] "
                lines.append(prefix + 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 the contents of a file. Returns the file content with line numbers."
    parameters = {
        "path": {"type": "string", "description": "Path to the file to read", "required": True},
        "offset": {"type": "number", "description": "Line number to start reading 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 pattern in a file. Returns matching lines with line numbers."
    parameters = {
        "path": {"type": "string", "description": "Path to the file to search", "required": True},
        "pattern": {"type": "string", "description": "Text pattern to search for", "required": True}
    }

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

        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(match['num']).rjust(4)}: {match['line']}" for match 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. Use this for surgical edits instead of write_file. The target text must exactly match the existing file content (including whitespace)."
    parameters = {
        "path": {"type": "string", "description": "Path to the file to edit", "required": True},
        "target": {"type": "string", "description": "The exact block of text to be replaced.", "required": True},
        "replacement": {"type": "string", "description": "The new block of text that replaces the target.", "required": True}
    }

    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", ""))

        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.")

            first_index = content.find(target)
            last_index = content.rfind(target)
            if first_index != last_index:
                return ToolResult(success=False, output="", error="edit_file failed: The target text appears multiple times. Please provide a larger block of text to uniquely identify the replacement.")

            new_content = content.replace(target, replacement)
            file_path.write_text(new_content, "utf-8")

            return ToolResult(success=True, output=f"Successfully edited {file_path}. Replaced {len(target)} bytes with {len(replacement)} bytes.")
        except Exception as e:
            return ToolResult(success=False, output="", error=f"edit_file failed: {str(e)}")


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