#!/usr/bin/env python3
from __future__ import annotations

import json
import mimetypes
import os
import subprocess
import sys
import threading
from datetime import datetime
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse

ROOT = Path(__file__).resolve().parents[1]
WEB_DIR = ROOT / "web"

STATIC_ROUTES: dict[str, Path] = {
    "/": WEB_DIR / "index.html",
    "/assets/styles.css": WEB_DIR / "styles.css",
    "/assets/app.js": WEB_DIR / "app.js",
}


class RunState:
    def __init__(self) -> None:
        self.lock = threading.Lock()
        self.running = False
        self.started_at: str | None = None
        self.finished_at: str | None = None
        self.exit_code: int | None = None
        self.command: list[str] = []
        self.logs: list[str] = []

    def snapshot(self) -> dict:
        with self.lock:
            return {
                "running": self.running,
                "started_at": self.started_at,
                "finished_at": self.finished_at,
                "exit_code": self.exit_code,
                "command": self.command,
                "logs": self.logs[-400:],
            }

    def start(self, cmd: list[str]) -> bool:
        with self.lock:
            if self.running:
                return False
            self.running = True
            self.started_at = datetime.now().isoformat(timespec="seconds")
            self.finished_at = None
            self.exit_code = None
            self.command = cmd
            self.logs = [f"[{self.started_at}] Starting: {' '.join(cmd)}"]
            return True

    def append_log(self, line: str) -> None:
        with self.lock:
            self.logs.append(line.rstrip("\n"))
            if len(self.logs) > 2000:
                self.logs = self.logs[-2000:]

    def finish(self, code: int) -> None:
        with self.lock:
            self.running = False
            self.exit_code = code
            self.finished_at = datetime.now().isoformat(timespec="seconds")
            self.logs.append(f"[{self.finished_at}] Finished with exit_code={code}")


STATE = RunState()


def _runner(cmd: list[str]) -> None:
    env = os.environ.copy()
    process = subprocess.Popen(
        cmd,
        cwd=str(ROOT),
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
        bufsize=1,
        env=env,
    )
    assert process.stdout is not None
    for line in process.stdout:
        STATE.append_log(line)
    process.wait()
    STATE.finish(process.returncode)


class Handler(BaseHTTPRequestHandler):
    def _send_json(self, payload: dict, status: int = 200) -> None:
        body = json.dumps(payload).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _send_bytes(self, body: bytes, content_type: str, status: int = 200) -> None:
        self.send_response(status)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _serve_static(self, path: str) -> bool:
        file_path = STATIC_ROUTES.get(path)
        if file_path is None or not file_path.is_file():
            return False

        body = file_path.read_bytes()
        if path == "/":
            content_type = "text/html; charset=utf-8"
        else:
            guessed, _ = mimetypes.guess_type(str(file_path))
            content_type = guessed or "application/octet-stream"

        self._send_bytes(body, content_type)
        return True

    def do_GET(self) -> None:  # noqa: N802
        path = urlparse(self.path).path
        if path == "/api/status":
            self._send_json(STATE.snapshot())
            return
        if self._serve_static(path):
            return
        self._send_json({"error": "Not found"}, HTTPStatus.NOT_FOUND)

    def do_POST(self) -> None:  # noqa: N802
        parsed = urlparse(self.path)
        if parsed.path != "/api/start":
            self._send_json({"error": "Not found"}, HTTPStatus.NOT_FOUND)
            return

        q = parse_qs(parsed.query)
        dry_run = q.get("dry_run", ["0"])[0] in {"1", "true", "yes"}
        batch_size = q.get("batch_size", [""])[0].strip()

        cmd = [sys.executable, "bin/run_pipeline.py"]
        if dry_run:
            cmd.append("--dry-run")
        if batch_size:
            cmd.extend(["--batch-size", batch_size])

        if not STATE.start(cmd):
            self._send_json({"ok": False, "message": "A run is already in progress"}, 409)
            return

        t = threading.Thread(target=_runner, args=(cmd,), daemon=True)
        t.start()
        self._send_json({"ok": True, "message": "Run started", "command": cmd})

    def log_message(self, fmt: str, *args) -> None:  # noqa: A003
        return


def main() -> int:
    if not WEB_DIR.is_dir():
        print(f"Missing web directory: {WEB_DIR}", file=sys.stderr)
        return 1

    host = "95.111.228.198"
    port = 8088
    server = ThreadingHTTPServer((host, port), Handler)
    print(f"Synapse launcher running at http://{host}:{port}")
    server.serve_forever()
    return 0


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