#!/usr/bin/env python3
# Author: Tom Sapletta · https://tom.sapletta.com
# Part of the ifURI solution.

"""URI handlers for starting the local PHP review panel on a node."""

from __future__ import annotations

import os
import shutil
import signal
import socket
import subprocess
import time
from pathlib import Path
from typing import Any

import urirun

CONNECTOR_ID = "invoice-panel"
conn = urirun.connector(CONNECTOR_ID, scheme="panel")

DEFAULT_ROOT = "/home/tom/Downloads/2026/5"
DEFAULT_HOST = "0.0.0.0"
DEFAULT_PORT = 8099


def _root(path: str) -> Path:
    return Path(path or DEFAULT_ROOT).expanduser().resolve()


def _state_dir(root: Path) -> Path:
    return root / ".state"


def _pid_path(root: Path, port: int) -> Path:
    return _state_dir(root) / f"panel-{port}.pid"


def _log_path(root: Path, port: int) -> Path:
    return _state_dir(root) / f"panel-{port}.log"


def _pid_running(pid: int) -> bool:
    if pid <= 0:
        return False
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        return True
    return True


def _read_pid(path: Path) -> int:
    try:
        return int(path.read_text(encoding="utf-8").strip())
    except (OSError, ValueError):
        return 0


def _port_open(host: str, port: int, timeout: float = 0.4) -> bool:
    probe_host = "127.0.0.1" if host in {"0.0.0.0", "::"} else host
    try:
        with socket.create_connection((probe_host, port), timeout=timeout):
            return True
    except OSError:
        return False


def _public_url(port: int) -> str:
    return f"http://192.168.188.201:{port}"


@conn.handler("php/query/status", isolated=True, meta={"label": "PHP review panel status"})
def panel_status(root: str = DEFAULT_ROOT, port: int = DEFAULT_PORT) -> dict[str, Any]:
    base = _root(root)
    pid_file = _pid_path(base, port)
    pid = _read_pid(pid_file)
    running = _pid_running(pid)
    port_open = _port_open("127.0.0.1", port)
    return {
        "ok": True,
        "connector": CONNECTOR_ID,
        "root": str(base),
        "port": port,
        "pid": pid,
        "running": running,
        "portOpen": port_open,
        "url": _public_url(port),
        "pidFile": str(pid_file),
        "logFile": str(_log_path(base, port)),
        "indexExists": (base / "index.php").is_file(),
        "runShExists": (base / "run.sh").is_file(),
        "php": shutil.which("php") or "",
    }


@conn.handler("php/command/start", isolated=True, external=True,
              meta={"label": "Start PHP review panel"})
def panel_start(
    root: str = DEFAULT_ROOT,
    host: str = DEFAULT_HOST,
    port: int = DEFAULT_PORT,
    router: str = "index.php",
) -> dict[str, Any]:
    base = _root(root)
    if not base.is_dir():
        return {"ok": False, "connector": CONNECTOR_ID, "error": f"root does not exist: {base}"}
    index = base / router
    if not index.is_file():
        return {"ok": False, "connector": CONNECTOR_ID, "error": f"router does not exist: {index}"}
    php = shutil.which("php")
    if not php:
        return {"ok": False, "connector": CONNECTOR_ID, "error": "php not found"}

    status = panel_status(str(base), port)
    if status["running"] and status["portOpen"]:
        status["started"] = False
        status["reason"] = "already running"
        return status

    state = _state_dir(base)
    state.mkdir(parents=True, exist_ok=True)
    log_file = _log_path(base, port)
    log = log_file.open("ab")
    cmd = [php, "-S", f"{host}:{port}", "-t", str(base), str(index)]
    proc = subprocess.Popen(
        cmd,
        cwd=str(base),
        stdout=log,
        stderr=subprocess.STDOUT,
        stdin=subprocess.DEVNULL,
        start_new_session=True,
        close_fds=True,
    )
    _pid_path(base, port).write_text(str(proc.pid), encoding="utf-8")
    time.sleep(0.7)
    status = panel_status(str(base), port)
    status.update({
        "started": status["running"],
        "command": cmd,
        "host": host,
    })
    return status


@conn.handler("php/command/stop", isolated=True, external=True,
              meta={"label": "Stop PHP review panel"})
def panel_stop(root: str = DEFAULT_ROOT, port: int = DEFAULT_PORT) -> dict[str, Any]:
    base = _root(root)
    pid_file = _pid_path(base, port)
    pid = _read_pid(pid_file)
    if not _pid_running(pid):
        return {
            "ok": True,
            "connector": CONNECTOR_ID,
            "root": str(base),
            "port": port,
            "pid": pid,
            "stopped": False,
            "reason": "not running",
        }
    try:
        os.killpg(pid, signal.SIGTERM)
    except ProcessLookupError:
        pass
    except OSError:
        os.kill(pid, signal.SIGTERM)
    for _ in range(20):
        if not _pid_running(pid):
            break
        time.sleep(0.1)
    stopped = not _pid_running(pid)
    return {
        "ok": stopped,
        "connector": CONNECTOR_ID,
        "root": str(base),
        "port": port,
        "pid": pid,
        "stopped": stopped,
        "running": _pid_running(pid),
    }


def urirun_bindings() -> dict[str, Any]:
    return conn.bindings()


def main(argv: list[str] | None = None) -> int:
    return conn.cli(argv)


if __name__ == "__main__":
    raise SystemExit(main())
