#!/usr/bin/env python3
"""Generic MCP server template for wrapping a CLI command.

Rename environment keys and tools for your project.
"""

from __future__ import annotations

import json
import os
import shutil
import subprocess
from pathlib import Path
from typing import Any

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("project-template")


VALID_OUTPUT_FORMATS = {"text", "json"}
TEMPLATE_VERSION = "2026.03.12"


def _load_dotenv(path: Path) -> None:
    if not path.exists():
        return
    for raw_line in path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        key = key.strip()
        value = value.strip()
        if key and key not in os.environ:
            os.environ[key] = value


_load_dotenv(Path(__file__).with_name(".env"))


def _split_roots(env_key: str, defaults: list[Path]) -> list[Path]:
    raw = os.getenv(env_key, "").strip()
    if not raw:
        return [p.resolve() for p in defaults]
    roots: list[Path] = []
    for item in raw.split(","):
        item = item.strip()
        if item:
            roots.append(Path(item).expanduser().resolve())
    return roots


def _cwd() -> Path:
    return Path.cwd().resolve()


def _read_roots() -> list[Path]:
    c = _cwd()
    return _split_roots("PROJECT_ALLOWED_READ_DIRS", [c])


def _write_roots() -> list[Path]:
    c = _cwd()
    return _split_roots("PROJECT_ALLOWED_WRITE_DIRS", [c])


def _is_within(path: Path, roots: list[Path]) -> bool:
    rp = path.resolve()
    for root in roots:
        try:
            rp.relative_to(root)
            return True
        except ValueError:
            continue
    return False


def _validate_read_path(path_str: str, label: str) -> Path:
    p = Path(path_str).expanduser().resolve()
    if not _is_within(p, _read_roots()):
        raise ValueError(f"{label} must be inside allowed read dirs: {p}")
    return p


def _validate_write_path(path_str: str, label: str) -> Path:
    p = Path(path_str).expanduser().resolve()
    if not _is_within(p.parent, _write_roots()):
        raise ValueError(f"{label} must be inside allowed write dirs: {p}")
    return p


def _project_bin() -> str:
    configured = os.getenv("PROJECT_BIN", "").strip()
    if configured:
        p = Path(configured).expanduser().resolve()
        if not p.exists():
            raise RuntimeError(f"PROJECT_BIN does not exist: {p}")
        return str(p)

    # fallback to a generic binary name on PATH
    located = shutil.which("project-cli")
    if not located:
        raise RuntimeError("project binary not found. Set PROJECT_BIN.")
    return located


def _timeout_sec(requested: int | None) -> int:
    env_default = int(os.getenv("PROJECT_CMD_TIMEOUT_SEC", "180"))
    if requested is None:
        return env_default
    if requested <= 0:
        raise ValueError("timeout_sec must be > 0")
    return requested


def _run(args: list[str], timeout_sec: int | None = None) -> dict[str, Any]:
    timeout = _timeout_sec(timeout_sec)
    cmd = [_project_bin(), *args]
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
        )
        return {
            "ok": proc.returncode == 0,
            "return_code": proc.returncode,
            "cmd": cmd,
            "stdout": proc.stdout.strip(),
            "stderr": proc.stderr.strip(),
            "timeout_sec": timeout,
        }
    except subprocess.TimeoutExpired as exc:
        return {
            "ok": False,
            "return_code": 124,
            "cmd": cmd,
            "stdout": (exc.stdout or "").strip() if exc.stdout else "",
            "stderr": "command timed out",
            "timeout_sec": timeout,
        }


@mcp.tool()
def version() -> str:
    configured = os.getenv("PROJECT_BIN", "").strip()
    resolved_bin = ""
    if configured:
        resolved_bin = str(Path(configured).expanduser().resolve())
    else:
        located = shutil.which("project-cli")
        if located:
            resolved_bin = str(Path(located).expanduser().resolve())

    data = {
        "server_name": "project-template",
        "template_version": TEMPLATE_VERSION,
        "project_bin_env": "PROJECT_BIN",
        "resolved_bin": resolved_bin,
        "read_roots": [str(path) for path in _read_roots()],
        "write_roots": [str(path) for path in _write_roots()],
    }
    return json.dumps(data, ensure_ascii=True)


@mcp.tool()
def paths() -> str:
    data = {
        "cwd": str(_cwd()),
        "dotenv_path": str(Path(__file__).with_name(".env")),
        "server_path": str(Path(__file__).resolve()),
        "resolved_bin": os.getenv("PROJECT_BIN", "").strip() or shutil.which("project-cli") or "",
        "read_roots": [str(path) for path in _read_roots()],
        "write_roots": [str(path) for path in _write_roots()],
    }
    return json.dumps(data, ensure_ascii=True)


@mcp.tool()
def healthcheck(timeout_sec: int | None = 20) -> str:
    return json.dumps(_run(["--help"], timeout_sec), ensure_ascii=True)


@mcp.tool()
def run_example(
    input_path: str,
    out_path: str,
    output_format: str = "text",
    timeout_sec: int | None = None,
) -> str:
    if output_format not in VALID_OUTPUT_FORMATS:
        raise ValueError(f"output_format must be one of {sorted(VALID_OUTPUT_FORMATS)}")

    in_path = _validate_read_path(input_path, "input_path")
    out = _validate_write_path(out_path, "out_path")

    # Replace this command mapping with your own project command shape.
    cmd = ["run", "--input", str(in_path), "--out", str(out), "--format", output_format]
    return json.dumps(_run(cmd, timeout_sec), ensure_ascii=True)


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