"""File system tools: read, write, edit, memory_append."""

from datetime import datetime
from pathlib import Path
from typing import Any

from flowly.agent.tools.base import Tool

# Allowed paths outside workspace (home-relative config/data dirs)
_ALLOWED_PREFIXES = (
    Path.home() / ".flowly",
)

# Sensitive paths that must never be readable by the agent
_DENIED_PATHS = (
    Path.home() / ".flowly" / "config.json",
    Path.home() / ".flowly" / "credentials",
    Path.home() / ".flowly" / "electron-api.json",
    Path.home() / ".flowly" / "sessions",
)


def _is_path_allowed(resolved_path: Path, workspace: Path | None) -> bool:
    """Check if a resolved path is within workspace or allowed prefixes."""
    # Deny sensitive files even within allowed prefixes
    for denied in _DENIED_PATHS:
        try:
            resolved_path.relative_to(denied.resolve())
            return False
        except ValueError:
            continue
        except OSError:
            continue

    # Always allow flowly data directory
    for prefix in _ALLOWED_PREFIXES:
        try:
            resolved_path.relative_to(prefix.resolve())
            return True
        except ValueError:
            continue

    # Allow workspace and its children
    if workspace:
        try:
            resolved_path.relative_to(workspace.resolve())
            return True
        except ValueError:
            pass

    return False


class ReadFileTool(Tool):
    """Tool to read file contents."""

    def __init__(self, workspace: Path | None = None):
        self.workspace = workspace

    @property
    def name(self) -> str:
        return "read_file"

    @property
    def description(self) -> str:
        return "Read the contents of a file at the given path."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The file path to read"
                }
            },
            "required": ["path"]
        }

    async def execute(self, path: str, **kwargs: Any) -> str:
        try:
            file_path = Path(path).expanduser().resolve()

            if not _is_path_allowed(file_path, self.workspace):
                return f"Error: Access denied — path outside workspace: {path}"

            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}"

            content = file_path.read_text(encoding="utf-8")
            return content
        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error reading file: {str(e)}"


class WriteFileTool(Tool):
    """Tool to write content to a file."""

    def __init__(self, workspace: Path | None = None):
        self.workspace = workspace

    @property
    def name(self) -> str:
        return "write_file"

    @property
    def description(self) -> str:
        return "Write content to a file at the given path. Creates parent directories if needed."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The file path to write to"
                },
                "content": {
                    "type": "string",
                    "description": "The content to write"
                }
            },
            "required": ["path", "content"]
        }

    async def execute(self, path: str, content: str, **kwargs: Any) -> str:
        try:
            file_path = Path(path).expanduser().resolve()

            if not _is_path_allowed(file_path, self.workspace):
                return f"Error: Access denied — path outside workspace: {path}"

            file_path.parent.mkdir(parents=True, exist_ok=True)
            file_path.write_text(content, encoding="utf-8")
            return f"Successfully wrote {len(content)} bytes to {path}"
        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error writing file: {str(e)}"


class EditFileTool(Tool):
    """Tool to edit a file by replacing text."""

    def __init__(self, workspace: Path | None = None):
        self.workspace = workspace

    @property
    def name(self) -> str:
        return "edit_file"

    @property
    def description(self) -> str:
        return "Edit a file by replacing old_text with new_text. The old_text must exist exactly in the file."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The file path to edit"
                },
                "old_text": {
                    "type": "string",
                    "description": "The exact text to find and replace"
                },
                "new_text": {
                    "type": "string",
                    "description": "The text to replace with"
                }
            },
            "required": ["path", "old_text", "new_text"]
        }

    async def execute(self, path: str, old_text: str, new_text: str, **kwargs: Any) -> str:
        try:
            file_path = Path(path).expanduser().resolve()

            if not _is_path_allowed(file_path, self.workspace):
                return f"Error: Access denied — path outside workspace: {path}"

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

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

            if old_text not in content:
                return f"Error: old_text not found in file. Make sure it matches exactly."

            # Count occurrences
            count = content.count(old_text)
            if count > 1:
                return f"Warning: old_text appears {count} times. Please provide more context to make it unique."

            new_content = content.replace(old_text, new_text, 1)
            file_path.write_text(new_content, encoding="utf-8")

            return f"Successfully edited {path}"
        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error editing file: {str(e)}"


class ListDirTool(Tool):
    """Tool to list directory contents."""

    def __init__(self, workspace: Path | None = None):
        self.workspace = workspace

    @property
    def name(self) -> str:
        return "list_dir"

    @property
    def description(self) -> str:
        return "List the contents of a directory."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The directory path to list"
                }
            },
            "required": ["path"]
        }

    async def execute(self, path: str, **kwargs: Any) -> str:
        try:
            dir_path = Path(path).expanduser().resolve()

            if not _is_path_allowed(dir_path, self.workspace):
                return f"Error: Access denied — path outside workspace: {path}"

            if not dir_path.exists():
                return f"Error: Directory not found: {path}"
            if not dir_path.is_dir():
                return f"Error: Not a directory: {path}"

            items = []
            for item in sorted(dir_path.iterdir()):
                prefix = "d " if item.is_dir() else "f "
                items.append(f"{prefix}{item.name}")

            if not items:
                return f"Directory {path} is empty"

            return "\n".join(items)
        except PermissionError:
            return f"Error: Permission denied: {path}"
        except Exception as e:
            return f"Error listing directory: {str(e)}"


class MemoryAppendTool(Tool):
    """
    Safely append a note to memory/MEMORY.md.

    Unlike write_file (which overwrites) or edit_file (which requires exact match),
    this tool atomically appends content with a timestamp. Use it whenever you learn
    something important about the user or want to record a fact for later.
    """

    def __init__(self, workspace: Path):
        self.workspace = workspace

    @property
    def name(self) -> str:
        return "memory_append"

    @property
    def description(self) -> str:
        return (
            "Append a note to long-term memory (memory/MEMORY.md). "
            "Use this whenever you learn something important about the user: "
            "their name, preferences, ongoing projects, commitments, or any fact worth remembering. "
            "Safer than write_file — never overwrites existing content."
        )

    @property
    def parameters(self) -> dict:
        return {
            "type": "object",
            "properties": {
                "content": {
                    "type": "string",
                    "description": "The note to append. Be concise and factual.",
                }
            },
            "required": ["content"],
        }

    async def execute(self, content: str, **kwargs: Any) -> str:
        try:
            memory_dir = self.workspace / "memory"
            memory_dir.mkdir(parents=True, exist_ok=True)
            memory_file = memory_dir / "MEMORY.md"

            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
            note = f"\n\n<!-- {timestamp} -->\n{content.strip()}"

            with open(memory_file, "a", encoding="utf-8") as f:
                f.write(note)

            return f"Appended to MEMORY.md ({len(content)} chars)"
        except Exception as e:
            return f"Error writing memory: {str(e)}"
