"""
Filesystem Tools

Read, write, append, list, and delete files in the workspace.
Paths are validated by the security engine before execution.
"""

import os
import logging
from typing import Dict, Any
from pathlib import Path

from .base import BaseTool, ToolMetadata, ToolParameter, ToolPermission, ToolExecutionContext, ToolResult

logger = logging.getLogger(__name__)


class ReadFileTool(BaseTool):
    """Read contents of a file."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="READ_FILE",
            description="Read the contents of a file. Returns the file text content.",
            category="filesystem",
            parameters=[
                ToolParameter("file_path", "string", "Path to the file to read", required=True),
                ToolParameter("encoding", "string", "File encoding", default="utf-8"),
                ToolParameter("start_line", "integer", "Start reading from this line (1-indexed)"),
                ToolParameter("end_line", "integer", "Stop reading at this line (inclusive)"),
            ],
            permissions=[ToolPermission.READ],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        file_path = params.get("file_path", "")
        encoding = params.get("encoding", "utf-8")
        start_line = params.get("start_line")
        end_line = params.get("end_line")

        if not file_path:
            return ToolResult.fail("file_path is required")

        # Resolve relative to workspace
        resolved = self._resolve_path(file_path, context)

        if not os.path.exists(resolved):
            return ToolResult.fail(f"File not found: {file_path}")
        if not os.path.isfile(resolved):
            return ToolResult.fail(f"Not a file: {file_path}")

        try:
            with open(resolved, "r", encoding=encoding, errors="replace") as f:
                if start_line or end_line:
                    lines = f.readlines()
                    start = (start_line or 1) - 1
                    end = end_line or len(lines)
                    content = "".join(lines[start:end])
                    return ToolResult.ok(content, total_lines=len(lines),
                                         showing=f"{start+1}-{min(end, len(lines))}")
                else:
                    content = f.read()
                    return ToolResult.ok(content, file_size=os.path.getsize(resolved))
        except Exception as e:
            return ToolResult.fail(f"Error reading file: {e}")

    @staticmethod
    def _resolve_path(file_path: str, context: ToolExecutionContext) -> str:
        if os.path.isabs(file_path):
            return file_path
        return os.path.join(context.get_cwd(), file_path)


class WriteFileTool(BaseTool):
    """Write content to a file (creates or overwrites)."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="WRITE_FILE",
            description="Write content to a file. Creates the file and parent directories if needed.",
            category="filesystem",
            parameters=[
                ToolParameter("file_path", "string", "Path to write to", required=True),
                ToolParameter("content", "string", "Content to write", required=True),
                ToolParameter("encoding", "string", "File encoding", default="utf-8"),
            ],
            permissions=[ToolPermission.WRITE],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        file_path = params.get("file_path", "")
        content = params.get("content", "")
        encoding = params.get("encoding", "utf-8")

        if not file_path:
            return ToolResult.fail("file_path is required")

        resolved = ReadFileTool._resolve_path(file_path, context)

        try:
            os.makedirs(os.path.dirname(resolved), exist_ok=True)
            with open(resolved, "w", encoding=encoding) as f:
                f.write(content)
            size = os.path.getsize(resolved)
            return ToolResult.ok(f"Written {size} bytes to {file_path}", file_size=size)
        except Exception as e:
            return ToolResult.fail(f"Error writing file: {e}")


class AppendFileTool(BaseTool):
    """Append content to a file."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="APPEND_FILE",
            description="Append content to the end of a file.",
            category="filesystem",
            parameters=[
                ToolParameter("file_path", "string", "Path to the file", required=True),
                ToolParameter("content", "string", "Content to append", required=True),
            ],
            permissions=[ToolPermission.WRITE],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        file_path = params.get("file_path", "")
        content = params.get("content", "")

        if not file_path:
            return ToolResult.fail("file_path is required")

        resolved = ReadFileTool._resolve_path(file_path, context)

        try:
            os.makedirs(os.path.dirname(resolved), exist_ok=True)
            with open(resolved, "a", encoding="utf-8") as f:
                f.write(content)
            return ToolResult.ok(f"Appended {len(content)} characters to {file_path}")
        except Exception as e:
            return ToolResult.fail(f"Error appending to file: {e}")


class DeleteFileTool(BaseTool):
    """Delete a file."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="DELETE_FILE",
            description="Delete a file from the filesystem.",
            category="filesystem",
            parameters=[
                ToolParameter("file_path", "string", "Path to the file to delete", required=True),
            ],
            permissions=[ToolPermission.WRITE],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        file_path = params.get("file_path", "")
        if not file_path:
            return ToolResult.fail("file_path is required")

        resolved = ReadFileTool._resolve_path(file_path, context)

        if not os.path.exists(resolved):
            return ToolResult.fail(f"File not found: {file_path}")

        try:
            os.remove(resolved)
            return ToolResult.ok(f"Deleted: {file_path}")
        except Exception as e:
            return ToolResult.fail(f"Error deleting file: {e}")


class ListDirectoryTool(BaseTool):
    """List contents of a directory."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="LIST_DIRECTORY",
            description="List files and directories in a path.",
            category="filesystem",
            parameters=[
                ToolParameter("path", "string", "Directory path to list", default="."),
                ToolParameter("recursive", "boolean", "List recursively", default=False),
                ToolParameter("max_depth", "integer", "Max recursion depth", default=2),
            ],
            permissions=[ToolPermission.READ],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        dir_path = params.get("path", ".")
        recursive = params.get("recursive", False)
        max_depth = params.get("max_depth", 2)

        resolved = ReadFileTool._resolve_path(dir_path, context)

        if not os.path.exists(resolved):
            return ToolResult.fail(f"Directory not found: {dir_path}")
        if not os.path.isdir(resolved):
            return ToolResult.fail(f"Not a directory: {dir_path}")

        try:
            entries = []
            if recursive:
                self._walk(resolved, entries, 0, max_depth)
            else:
                for item in sorted(os.listdir(resolved)):
                    full = os.path.join(resolved, item)
                    entry_type = "dir" if os.path.isdir(full) else "file"
                    size = os.path.getsize(full) if os.path.isfile(full) else 0
                    entries.append(f"{'📁' if entry_type == 'dir' else '📄'} {item}" +
                                   (f" ({self._human_size(size)})" if size else ""))

            if not entries:
                return ToolResult.ok("(empty directory)")

            return ToolResult.ok("\n".join(entries), item_count=len(entries))
        except Exception as e:
            return ToolResult.fail(f"Error listing directory: {e}")

    def _walk(self, path: str, entries: list, depth: int, max_depth: int):
        if depth > max_depth or len(entries) > 500:
            return
        indent = "  " * depth
        for item in sorted(os.listdir(path)):
            full = os.path.join(path, item)
            if os.path.isdir(full):
                entries.append(f"{indent}📁 {item}/")
                self._walk(full, entries, depth + 1, max_depth)
            else:
                size = os.path.getsize(full)
                entries.append(f"{indent}📄 {item} ({self._human_size(size)})")

    @staticmethod
    def _human_size(size: int) -> str:
        for unit in ["B", "KB", "MB", "GB"]:
            if size < 1024:
                return f"{size:.0f}{unit}" if unit == "B" else f"{size:.1f}{unit}"
            size /= 1024
        return f"{size:.1f}TB"


def get_all_tools():
    return [ReadFileTool, WriteFileTool, AppendFileTool, DeleteFileTool, ListDirectoryTool]
