import asyncio
import os
import signal

# Module-level: tracks running servers across tool calls within a session
_SERVERS: dict[str, dict] = {}  # name -> {"process", "command", "directory", "port", "pid"}

STARTUP_WAIT = 1.0  # seconds to wait for crash detection
STOP_TIMEOUT = 5  # seconds before SIGKILL


async def handler(params: dict) -> dict:
    """Manage dev servers."""
    action = params["action"]

    if action == "start":
        return await _start(params)
    if action == "stop":
        return await _stop(params)
    if action == "status":
        return _status(params)
    if action == "list":
        return _list_servers()
    return {"status": "", "error": f"Unknown action: {action}. Use: start, stop, status, list."}


async def _start(params: dict) -> dict:
    name = params.get("name", "")
    command = params.get("command", "")
    directory = params.get("directory") or os.getcwd()
    port = params.get("port")

    if not name:
        return {"status": "", "error": "Server name required."}
    if not command:
        return {"status": "", "error": "Command required for start."}

    # Check if already running
    if name in _SERVERS:
        existing = _SERVERS[name]
        proc = existing["process"]
        if proc.returncode is None:
            return {"status": "", "error": f"Server '{name}' already running (PID {proc.pid})."}
        # Previous process exited, clean up
        del _SERVERS[name]

    try:
        proc = await asyncio.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.PIPE,
            cwd=directory,
            preexec_fn=os.setsid,
        )
    except Exception as e:
        return {"status": "", "error": f"Failed to start: {e}"}

    # Wait briefly to detect immediate crashes
    try:
        await asyncio.wait_for(proc.wait(), timeout=STARTUP_WAIT)
        # Process exited within STARTUP_WAIT — it crashed
        stderr_bytes = await proc.stderr.read()
        stderr = stderr_bytes.decode("utf-8", errors="replace")[:2000]
        return {"status": "", "error": f"Server crashed immediately (exit {proc.returncode}): {stderr}"}
    except asyncio.TimeoutError:
        # Still running — good
        pass

    _SERVERS[name] = {
        "process": proc,
        "command": command,
        "directory": directory,
        "port": port,
        "pid": proc.pid,
    }

    port_info = f" on port {port}" if port else ""
    return {"status": f"Started '{name}' (PID {proc.pid}){port_info}", "error": ""}


async def _stop(params: dict) -> dict:
    name = params.get("name", "")
    if not name:
        return {"status": "", "error": "Server name required."}

    if name not in _SERVERS:
        return {"status": "", "error": f"No server named '{name}' found."}

    entry = _SERVERS.pop(name)
    proc = entry["process"]

    if proc.returncode is not None:
        return {"status": f"Server '{name}' already exited (code {proc.returncode}).", "error": ""}

    # Send SIGTERM to process group
    try:
        pgid = os.getpgid(proc.pid)
        os.killpg(pgid, signal.SIGTERM)
    except ProcessLookupError:
        return {"status": f"Server '{name}' already stopped.", "error": ""}

    # Wait for graceful shutdown
    try:
        await asyncio.wait_for(proc.wait(), timeout=STOP_TIMEOUT)
        return {"status": f"Stopped '{name}' (SIGTERM).", "error": ""}
    except asyncio.TimeoutError:
        pass

    # Force kill
    try:
        pgid = os.getpgid(proc.pid)
        os.killpg(pgid, signal.SIGKILL)
        await proc.wait()
    except ProcessLookupError:
        pass

    return {"status": f"Stopped '{name}' (SIGKILL after {STOP_TIMEOUT}s timeout).", "error": ""}


def _status(params: dict) -> dict:
    name = params.get("name", "")
    if not name:
        return {"status": "", "error": "Server name required."}

    if name not in _SERVERS:
        return {"status": f"No server named '{name}' tracked.", "error": ""}

    entry = _SERVERS[name]
    proc = entry["process"]
    port_info = f", port {entry['port']}" if entry.get("port") else ""

    if proc.returncode is not None:
        return {"status": f"'{name}' exited (code {proc.returncode}){port_info}", "error": ""}
    return {"status": f"'{name}' running (PID {proc.pid}){port_info}", "error": ""}


def _list_servers() -> dict:
    if not _SERVERS:
        return {"status": "No servers tracked.", "error": ""}

    lines = []
    for name, entry in _SERVERS.items():
        proc = entry["process"]
        port_info = f", port {entry['port']}" if entry.get("port") else ""
        state = "running" if proc.returncode is None else f"exited({proc.returncode})"
        lines.append(f"  {name}: {state} (PID {entry['pid']}){port_info}")

    return {"status": "Tracked servers:\n" + "\n".join(lines), "error": ""}
