"""Minimal local HTTP server for the domesday web UI.

Serves the web UI and exposes a JSON API so the browser can
add snippets directly to the database.

Started via ``domes server``. Ctrl-C to stop.
"""

from __future__ import annotations

import json
import logging
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer
from importlib.resources import files
from pathlib import Path

from domesday.core.pipeline import Pipeline

logger = logging.getLogger(__name__)

_STATIC_DIR = Path(str(files("domesday").joinpath("web")))


class WebHandler(BaseHTTPRequestHandler):
    """Handles GET / (HTML), GET /api/metadata, POST /api/add."""

    pipeline: Pipeline  # set by the factory

    def do_GET(self) -> None:
        if self.path == "/" or self.path == "/index.html":
            self._serve_file("snippet_inbox.html", "text/html; charset=utf-8")
        elif self.path == "/sw.js":
            self._serve_sw()
        elif self.path == "/api/metadata":
            self._serve_metadata()
        else:
            self.send_error(HTTPStatus.NOT_FOUND)

    def do_POST(self) -> None:
        if self.path == "/api/add":
            self._handle_add()
        else:
            self.send_error(HTTPStatus.NOT_FOUND)

    # ---------------------------------------------------------------
    # Handlers
    # ---------------------------------------------------------------

    def _serve_file(self, filename: str, content_type: str) -> None:
        path = _STATIC_DIR / filename
        content = path.read_bytes()
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(content)))
        self.end_headers()
        self.wfile.write(content)

    def _serve_sw(self) -> None:
        """Serve sw.js with __VERSION__ replaced by the package version."""
        import importlib.metadata

        version = importlib.metadata.version("domesday")
        path = _STATIC_DIR / "sw.js"
        content = path.read_text(encoding="utf-8").replace("__VERSION__", version)
        body = content.encode()
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", "application/javascript")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _serve_metadata(self) -> None:
        """Return existing projects, authors, tags from the database."""
        projects: set[str] = set()
        authors: set[str] = set()
        tags: set[str] = set()

        for s in self.pipeline.snippet_store.get_all_active():
            projects.add(s.project)
            if s.author:
                authors.add(s.author)
            for t in s.tags:
                if t:
                    tags.add(t)

        self._json_response(
            {
                "projects": sorted(projects),
                "authors": sorted(authors),
                "tags": sorted(tags),
                "default_project": self.pipeline.default_project,
            }
        )

    def _handle_add(self) -> None:
        """Add a snippet directly to the database."""
        try:
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
        except (ValueError, json.JSONDecodeError) as exc:
            self._json_response({"error": str(exc)}, status=HTTPStatus.BAD_REQUEST)
            return

        raw_text = body.get("raw_text", "").strip()
        if not raw_text:
            self._json_response(
                {"error": "raw_text is required"}, status=HTTPStatus.BAD_REQUEST
            )
            return

        project = body.get("project") or self.pipeline.default_project
        author = body.get("author") or "web"
        tag_list = body.get("tags", [])
        if isinstance(tag_list, str):
            tag_list = [t.strip() for t in tag_list.split(",") if t.strip()]

        from domesday.core.models import SnippetType

        try:
            snippet_type = SnippetType(body.get("snippet_type", "prose"))
        except ValueError:
            snippet_type = SnippetType.PROSE

        try:
            snippet = self.pipeline.add_snippet(
                raw_text,
                project=project,
                author=author,
                tags=tag_list,
                snippet_type=snippet_type,
            )
        except Exception as exc:
            logger.exception("Failed to add snippet")
            self._json_response(
                {"error": str(exc)}, status=HTTPStatus.INTERNAL_SERVER_ERROR
            )
            return

        self._json_response(
            {
                "ok": True,
                "snippet_id": snippet.id[:8],
                "project": snippet.project,
            }
        )

    # ---------------------------------------------------------------
    # Helpers
    # ---------------------------------------------------------------

    def _json_response(
        self,
        data: dict,
        *,
        status: HTTPStatus = HTTPStatus.OK,
        content_type: str = "application/json",
    ) -> None:
        body = json.dumps(data).encode()
        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 log_message(self, format: str, *args: object) -> None:
        logger.debug(format, *args)


def make_handler(pipeline: Pipeline) -> type[WebHandler]:
    """Create a handler class with the pipeline bound."""
    return type("_BoundWebHandler", (WebHandler,), {"pipeline": pipeline})


def run_server(pipeline: Pipeline, *, port: int = 8765) -> None:
    """Start the HTTP server. Blocks until Ctrl-C."""
    handler_cls = make_handler(pipeline)
    server = HTTPServer(("127.0.0.1", port), handler_cls)
    logger.info("Web server listening on http://127.0.0.1:%d", port)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()
        logger.info("Web server stopped")
