#!/usr/bin/env python3
"""
BSD Watermark Studio — MCP server (stdio)

Claude Code / Codex / Cursor 에서 등록하면:
  - start_studio  : 로컬 글래스 UI 실행 + 브라우저 오픈
  - watermark_pdf : PDF에 BSD CLASS LICENSE 사선 + 법률 페이지 삽입
  - studio_status : UI 서버 상태
  - stop_studio   : UI 서버 종료
"""

from __future__ import annotations

import os
import signal
import socket
import subprocess
import sys
import time
import urllib.request
from datetime import date
from pathlib import Path

# Frozen exe: resources in _MEIPASS; writable data in BSD_WM_DATA
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
    ROOT = Path(sys._MEIPASS)  # type: ignore[attr-defined]
else:
    ROOT = Path(__file__).resolve().parent
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))
_DATA = Path(os.environ["BSD_WM_DATA"]) if os.environ.get("BSD_WM_DATA") else ROOT

from mcp.server.fastmcp import FastMCP

from watermark_pdf import process_pdf

mcp = FastMCP(
    "bsd-watermark",
    instructions=(
        "BSD Watermark Studio. Start the local glass UI with start_studio, "
        "or watermark a PDF file with watermark_pdf (BSD CLASS LICENSE diagonal text + legal pages)."
    ),
)

PORT = int(os.environ.get("BSD_WM_PORT", "8765"))
HOST = "127.0.0.1"
URL = f"http://{HOST}:{PORT}"
_PID_FILE = _DATA / ".studio.pid"


def _venv_python() -> Path:
    if sys.platform == "win32":
        return ROOT / ".venv" / "Scripts" / "python.exe"
    return ROOT / ".venv" / "bin" / "python"


def _ensure_venv() -> Path:
    py = _venv_python()
    if not py.exists():
        import venv

        venv.create(ROOT / ".venv", with_pip=True)
        subprocess.check_call(
            [str(py), "-m", "pip", "install", "-q", "-r", str(ROOT / "requirements.txt")]
        )
    return py


def _port_open(port: int = PORT) -> bool:
    try:
        with socket.create_connection((HOST, port), timeout=0.4):
            return True
    except OSError:
        return False


def _http_ok() -> bool:
    try:
        with urllib.request.urlopen(URL + "/", timeout=1.5) as r:
            return 200 <= r.status < 400
    except Exception:
        return False


def _read_pid() -> int | None:
    try:
        return int(_PID_FILE.read_text().strip())
    except Exception:
        return None


def _write_pid(pid: int) -> None:
    _PID_FILE.write_text(str(pid), encoding="utf-8")


def _clear_pid() -> None:
    try:
        _PID_FILE.unlink(missing_ok=True)
    except Exception:
        pass


def _open_browser(url: str) -> None:
    try:
        if sys.platform == "darwin":
            subprocess.Popen(["open", url])
        elif sys.platform == "win32":
            os.startfile(url)  # type: ignore[attr-defined]
        else:
            subprocess.Popen(["xdg-open", url])
    except Exception:
        pass


@mcp.tool()
def start_studio(open_browser: bool = True) -> str:
    """
    Start the BSD Watermark Studio local web UI (dark glassmorphism).
    Opens http://127.0.0.1:8765 so the user can drag-and-drop PDFs.
    Call this when the user wants the watermark UI or to process PDFs visually.
    """
    if _http_ok():
        if open_browser:
            _open_browser(URL)
        return f"Studio already running at {URL}"

    env = os.environ.copy()
    env["BSD_WM_PORT"] = str(PORT)
    _DATA.mkdir(parents=True, exist_ok=True)
    log = open(_DATA / "studio.log", "a", encoding="utf-8")
    # Frozen: re-exec this binary as UI. Source tree: uvicorn.
    if getattr(sys, "frozen", False):
        cmd = [sys.executable]
        cwd = str(_DATA)
    else:
        py = _ensure_venv()
        cmd = [
            str(py),
            "-m",
            "uvicorn",
            "app:app",
            "--host",
            HOST,
            "--port",
            str(PORT),
        ]
        cwd = str(ROOT / "web")
    proc = subprocess.Popen(
        cmd,
        cwd=cwd,
        env=env,
        stdout=log,
        stderr=subprocess.STDOUT,
        start_new_session=True,
    )
    _write_pid(proc.pid)

    # wait for ready
    for _ in range(40):
        time.sleep(0.25)
        if _http_ok():
            if open_browser:
                _open_browser(URL)
            return (
                f"BSD Watermark Studio started.\n"
                f"URL: {URL}\n"
                f"Drag & drop a PDF, set buyer/order, generate watermarked PDF.\n"
                f"pid={proc.pid}"
            )
        if proc.poll() is not None:
            _clear_pid()
            return f"Failed to start studio (exit {proc.returncode}). See studio.log"

    return f"Studio process started (pid={proc.pid}) but not ready yet. Open {URL} manually."


@mcp.tool()
def studio_status() -> str:
    """Check whether the local Watermark Studio UI is running."""
    pid = _read_pid()
    up = _http_ok()
    port = _port_open()
    return (
        f"url={URL}\n"
        f"http_ok={up}\n"
        f"port_open={port}\n"
        f"pid_file={pid}\n"
        f"hint={'Open browser to ' + URL if up else 'Call start_studio'}"
    )


@mcp.tool()
def stop_studio() -> str:
    """Stop the local Watermark Studio UI server."""
    pid = _read_pid()
    stopped = False
    if pid:
        try:
            if sys.platform == "win32":
                subprocess.run(["taskkill", "/PID", str(pid), "/F"], check=False, capture_output=True)
            else:
                os.kill(pid, signal.SIGTERM)
            stopped = True
        except ProcessLookupError:
            pass
        except Exception as e:
            return f"Could not stop pid {pid}: {e}"
    _clear_pid()
    # best-effort free port
    if _port_open() and sys.platform != "win32":
        try:
            out = subprocess.check_output(["lsof", "-ti", f":{PORT}"], text=True).strip()
            for p in out.splitlines():
                try:
                    os.kill(int(p), signal.SIGTERM)
                    stopped = True
                except Exception:
                    pass
        except Exception:
            pass
    return "Studio stopped." if stopped or not _http_ok() else "Studio may still be running; try again."


@mcp.tool()
def watermark_pdf(
    input_path: str,
    output_path: str = "",
    text: str = "BSD CLASS LICENSE",
    buyer: str = "LICENSED USER",
    order: str = "N/A",
    opacity: float = 0.10,
    fontsize: float = 26,
    step: float = 110,
    legal_lang: str = "ko",
    no_legal: bool = False,
) -> str:
    """
    Apply BSD CLASS LICENSE diagonal text watermark to a PDF and insert legal pages
    (new page 2 + new last page). Does NOT use image watermarks.

    Args:
        input_path: Absolute path to source PDF
        output_path: Optional output path (default: <stem>_BSD_watermarked.pdf next to input)
        text: Diagonal watermark phrase
        buyer: Licensee name for legal page
        order: Order / ID for legal page
        opacity: Watermark opacity 0.05–0.22
        fontsize: Watermark font size
        step: Tile density (smaller = denser full-page fill)
        legal_lang: ko | en | both
        no_legal: If true, skip legal pages
    """
    _ensure_venv()
    src = Path(input_path).expanduser().resolve()
    if not src.exists():
        return f"ERROR: file not found: {src}"
    if src.suffix.lower() != ".pdf":
        return "ERROR: only PDF files are supported"

    if output_path:
        dst = Path(output_path).expanduser().resolve()
    else:
        dst = src.with_name(src.stem + "_BSD_watermarked.pdf")

    try:
        info = process_pdf(
            src,
            dst,
            text=(text or "BSD CLASS LICENSE").strip(),
            buyer=buyer or "LICENSED USER",
            order=order or "N/A",
            issued=date.today().isoformat(),
            opacity=float(opacity),
            fontsize=float(fontsize),
            step=float(step),
            add_legal=not no_legal,
            legal_lang=legal_lang if legal_lang in ("ko", "en", "both") else "ko",
        )
    except Exception as e:
        return f"ERROR: {e}"

    return (
        f"OK watermarked PDF\n"
        f"output: {info['output']}\n"
        f"pages: {info['pages']} (content {info.get('content_pages')} + legal)\n"
        f"legal_pages: {info.get('legal_pages_1based')}\n"
        f"mark: {text}"
    )


def main() -> None:
    # ensure project root on path when launched by MCP host
    os.chdir(ROOT)
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
