from __future__ import annotations

import argparse
import json
import mimetypes
import os
import subprocess
import tempfile
import threading
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any

from .converter import convert_pdf
from .paths import ensure_dir, project_root, safe_filename

_ACTIVE_JOB_LOCK = threading.Lock()
_ACTIVE_JOB: dict[str, Any] | None = None


def active_job_snapshot() -> dict:
    with _ACTIVE_JOB_LOCK:
        if not _ACTIVE_JOB:
            return {"active": False}
        return {
            "active": True,
            "job_id": _ACTIVE_JOB["job_id"],
            "filename": _ACTIVE_JOB["filename"],
        }


def begin_active_job(filename: str) -> dict | None:
    global _ACTIVE_JOB
    with _ACTIVE_JOB_LOCK:
        if _ACTIVE_JOB:
            return None
        job = {
            "job_id": next(tempfile._get_candidate_names()),
            "filename": filename,
            "cancel_event": threading.Event(),
        }
        _ACTIVE_JOB = job
        return job


def finish_active_job(job_id: str) -> None:
    global _ACTIVE_JOB
    with _ACTIVE_JOB_LOCK:
        if _ACTIVE_JOB and _ACTIVE_JOB["job_id"] == job_id:
            _ACTIVE_JOB = None


def cancel_active_job() -> dict:
    with _ACTIVE_JOB_LOCK:
        if not _ACTIVE_JOB:
            return {"ok": False, "error": "no_active_job"}
        _ACTIVE_JOB["cancel_event"].set()
        return {"ok": True, "job_id": _ACTIVE_JOB["job_id"]}


def json_response(payload: dict, status: int = 200, origin: str | None = None) -> tuple[int, dict[str, str], bytes]:
    allowed_origin = origin or "*"
    return (
        status,
        {
            "Content-Type": "application/json; charset=utf-8",
            "Access-Control-Allow-Origin": allowed_origin,
            "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
            "Access-Control-Allow-Headers": "Content-Type,X-Mode",
            "Access-Control-Allow-Private-Network": "true",
            "Vary": "Origin",
        },
        json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8"),
    )


def static_file_for_path(request_path: str) -> Path | None:
    parsed = urllib.parse.urlparse(request_path)
    raw_path = urllib.parse.unquote(parsed.path)
    relative_path = "index.html" if raw_path in {"", "/"} else raw_path.lstrip("/")
    if relative_path.startswith(("api/", "convert", "cancel", "health", "status", "open-output")):
        return None
    candidate = (project_root() / "app" / relative_path).resolve()
    app_root = (project_root() / "app").resolve()
    if app_root not in candidate.parents and candidate != app_root:
        return None
    return candidate if candidate.is_file() else None


def open_path(path: Path) -> None:
    if os.name == "nt":
        os.startfile(str(path))  # type: ignore[attr-defined]
        return
    opener = "open" if os.uname().sysname == "Darwin" else "xdg-open"
    subprocess.Popen([opener, str(path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


class WorkerHandler(BaseHTTPRequestHandler):
    server_version = "PdfToMdLocal/0.1"

    def _send_json(self, payload: dict, status: int = 200) -> None:
        code, headers, body = json_response(payload, status, self.headers.get("Origin"))
        self.send_response(code)
        for key, value in headers.items():
            self.send_header(key, value)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _send_file(self, path: Path) -> None:
        body = path.read_bytes()
        content_type = mimetypes.guess_type(str(path))[0] or "application/octet-stream"
        if path.name == "manifest.webmanifest":
            content_type = "application/manifest+json"
        self.send_response(200)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(body)))
        self.send_header("Cache-Control", "no-cache" if path.name == "sw.js" else "public, max-age=300")
        self.end_headers()
        self.wfile.write(body)

    def do_OPTIONS(self) -> None:
        self._send_json({"ok": True})

    def do_GET(self) -> None:
        if self.path.startswith("/health"):
            self._send_json({"ok": True, "service": "pdf-to-md-local-worker"})
            return
        if self.path.startswith("/status"):
            self._send_json({"ok": True, "job": active_job_snapshot()})
            return
        static_path = static_file_for_path(self.path)
        if static_path:
            self._send_file(static_path)
            return
        self._send_json({"ok": False, "error": "not_found"}, 404)

    def do_POST(self) -> None:
        parsed = urllib.parse.urlparse(self.path)
        if parsed.path == "/convert":
            query = urllib.parse.parse_qs(parsed.query)
            filename = safe_filename(query.get("filename", ["input.pdf"])[0])
            mode = self.headers.get("X-Mode", "auto")
            length = int(self.headers.get("Content-Length", "0"))
            if length <= 0:
                self._send_json({"ok": False, "error": "empty_request_body"}, 400)
                return
            job = begin_active_job(filename)
            if not job:
                self._send_json({"ok": False, "error": "job_already_running", "job": active_job_snapshot()}, 409)
                return
            job_dir = ensure_dir(project_root() / "work" / "jobs" / next(tempfile._get_candidate_names()))
            input_path = job_dir / filename
            input_path.write_bytes(self.rfile.read(length))
            try:
                result = convert_pdf(
                    input_path,
                    project_root() / "outputs",
                    mode,
                    job_id=job["job_id"],
                    cancel_event=job["cancel_event"],
                )
                self._send_json(
                    {"ok": result.status == "ok", "result": result.to_dict()},
                    200 if result.status == "ok" else 422,
                )
            finally:
                finish_active_job(job["job_id"])
            return
        if parsed.path == "/cancel":
            payload = cancel_active_job()
            self._send_json(payload, 200 if payload.get("ok") else 409)
            return
        if parsed.path == "/open-output":
            open_path(ensure_dir(project_root() / "outputs"))
            self._send_json({"ok": True})
            return
        self._send_json({"ok": False, "error": "not_found"}, 404)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8765)
    args = parser.parse_args()
    server = ThreadingHTTPServer((args.host, args.port), WorkerHandler)
    print(f"Worker running at http://{args.host}:{args.port}", flush=True)
    server.serve_forever()
    return 0


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