from __future__ import annotations

import os
import shutil
import sys
from pathlib import Path

from mcp.server.fastmcp import FastMCP

from tools._std import tool_error, tool_ok


mcp = FastMCP("filesystem")


def _expand(path: str) -> Path:
    return Path(os.path.expandvars(os.path.expanduser(str(path or "").strip()))).resolve()


def _allowed_roots() -> list[Path]:
    args = [a for a in sys.argv[1:] if str(a or "").strip()]
    roots = [_expand(a) for a in args] if args else [Path.cwd().resolve()]
    return roots


ROOTS = _allowed_roots()


def _resolve_in_roots(path: str) -> Path:
    raw = str(path or "").strip()
    if not raw:
        raise ValueError("path is required")

    candidate = _expand(raw)
    for root in ROOTS:
        try:
            candidate.relative_to(root)
            return candidate
        except ValueError:
            continue

    if not candidate.is_absolute():
        for root in ROOTS:
            joined = (root / candidate).resolve()
            try:
                joined.relative_to(root)
                return joined
            except ValueError:
                continue

    roots_text = ", ".join(str(r) for r in ROOTS)
    raise ValueError(f"path is outside allowed roots: {roots_text}")


@mcp.tool()
def fs_roots() -> dict:
    """List allowed filesystem roots for this server."""
    return {"roots": [str(r) for r in ROOTS]}


@mcp.tool()
def fs_list(path: str = ".") -> dict:
    """List files and folders under a directory."""
    target = _resolve_in_roots(path)
    if not target.exists():
        return {"ok": False, "error": f"path not found: {target}"}
    if not target.is_dir():
        return {"ok": False, "error": f"not a directory: {target}"}

    items = []
    for child in sorted(target.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())):
        items.append({
            "name": child.name,
            "path": str(child),
            "type": "dir" if child.is_dir() else "file",
            "size": child.stat().st_size if child.is_file() else None,
        })
    return {"ok": True, "path": str(target), "items": items}


@mcp.tool()
def fs_read(path: str, max_chars: int = 200000) -> dict:
    """Read a text file."""
    target = _resolve_in_roots(path)
    if not target.exists():
        return {"ok": False, "error": f"file not found: {target}"}
    if not target.is_file():
        return {"ok": False, "error": f"not a file: {target}"}

    text = target.read_text(encoding="utf-8", errors="replace")
    if max_chars > 0:
        text = text[:max_chars]
    return {"ok": True, "path": str(target), "content": text}


@mcp.tool()
def fs_write(path: str, content: str, overwrite: bool = True) -> dict:
    """Write a text file, creating parent directories when needed."""
    target = _resolve_in_roots(path)
    if target.exists() and not overwrite:
        return {"ok": False, "error": f"file exists and overwrite is false: {target}"}

    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(str(content or ""), encoding="utf-8")
    return {"ok": True, "path": str(target), "bytes": target.stat().st_size}


@mcp.tool()
def fs_mkdir(path: str, parents: bool = True, exist_ok: bool = True) -> dict:
    """Create a directory."""
    target = _resolve_in_roots(path)
    target.mkdir(parents=bool(parents), exist_ok=bool(exist_ok))
    return {"ok": True, "path": str(target)}


@mcp.tool()
def fs_copy(src: str, dst: str, overwrite: bool = True) -> dict:
    """Copy a file or directory."""
    source = _resolve_in_roots(src)
    target = _resolve_in_roots(dst)

    if not source.exists():
        return {"ok": False, "error": f"source not found: {source}"}
    if target.exists() and not overwrite:
        return {"ok": False, "error": f"destination exists and overwrite is false: {target}"}

    if source.is_dir():
        if target.exists() and target.is_file():
            return {"ok": False, "error": f"cannot copy directory to file: {target}"}
        if target.exists() and overwrite:
            shutil.rmtree(target)
        shutil.copytree(source, target)
    else:
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source, target)

    return {"ok": True, "src": str(source), "dst": str(target)}


@mcp.tool()
def fs_delete(path: str, recursive: bool = False) -> dict:
    """Delete a file or directory."""
    target = _resolve_in_roots(path)
    if not target.exists():
        return {"ok": True, "path": str(target), "deleted": False}

    if target.is_dir():
        if recursive:
            shutil.rmtree(target)
        else:
            target.rmdir()
    else:
        target.unlink()

    return {"ok": True, "path": str(target), "deleted": True}


if __name__ == "__main__":
    mcp.run()
