"""Lightweight local HTTP server for recs endpoints.

Started by the daemon as a background thread. Serves:
- GET  /health              — simple health check
- GET  /api/recs            — numbered rules from rules.json
- POST /api/recs/feedback   — accept/dismiss rules, relay to platform

No postgres, no ingest. Just file I/O + platform relay.
"""

from __future__ import annotations

import json
import logging
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse

from qc_trace.server.recs import (
    load_rules,
    number_rules,
    resolve_numbers_to_rules,
    resolve_slug_from_cache,
    send_feedback,
    update_rule_status,
)

logger = logging.getLogger("quickcall.local_server")


class LocalRecsHandler(BaseHTTPRequestHandler):
    """Handle recs-related HTTP requests using rules.json on disk."""

    # Set by create_local_recs_server
    base_dir: Path

    def log_message(self, format, *args):  # noqa: A002
        logger.debug(format, *args)

    def _send_json(self, data: dict, status: int = 200) -> None:
        body = json.dumps(data).encode()
        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_error(self, message: str, status: int = 400) -> None:
        self._send_json({"error": message}, status)

    def _parse_qs(self, path: str) -> dict[str, str]:
        parsed = urlparse(path)
        qs = parse_qs(parsed.query)
        return {k: v[0] for k, v in qs.items()}

    def do_GET(self) -> None:  # noqa: N802
        path = urlparse(self.path).path

        if path == "/health":
            self._send_json({"status": "ok"})
        elif path == "/api/recs":
            self._handle_get_recs()
        else:
            self._send_error("Not found", 404)

    def do_POST(self) -> None:  # noqa: N802
        path = urlparse(self.path).path

        if path == "/api/recs/feedback":
            content_length = int(self.headers.get("Content-Length", 0))
            raw_body = self.rfile.read(content_length)
            self._handle_post_feedback(raw_body)
        else:
            self._send_error("Not found", 404)

    def _handle_get_recs(self) -> None:
        qs = self._parse_qs(self.path)
        repo_slug = qs.get("repo")
        cwd = qs.get("cwd")

        if not repo_slug and not cwd:
            self._send_error("Missing required query parameter: repo or cwd")
            return

        if not repo_slug:
            repo_slug = resolve_slug_from_cache(cwd, self.base_dir)
            if not repo_slug:
                self._send_error(
                    f"Could not resolve repo for cwd={cwd}",
                    HTTPStatus.NOT_FOUND,
                )
                return

        rules_data = load_rules(self.base_dir, repo_slug)
        if rules_data is None:
            self._send_error("No rules found for repo", HTTPStatus.NOT_FOUND)
            return

        numbered = number_rules(rules_data)
        org_repo = repo_slug.replace("--", "/", 1)
        self._send_json({"repo": org_repo, "slug": repo_slug, "rules": numbered})

    def _handle_post_feedback(self, raw_body: bytes) -> None:
        try:
            body = json.loads(raw_body)
        except (json.JSONDecodeError, ValueError):
            self._send_error("Invalid JSON body")
            return

        # Legacy format
        if "rule_id" in body:
            rule_id = body.get("rule_id")
            action = body.get("action")
            repo = body.get("repo")

            if not rule_id or not action or not repo:
                self._send_error("Missing required fields: rule_id, action, repo")
                return
            if action not in ("accept", "dismiss"):
                self._send_error("action must be 'accept' or 'dismiss'")
                return

            status = "accepted" if action == "accept" else "dismissed"
            updated = update_rule_status(self.base_dir, repo, rule_id, status)
            if not updated:
                self._send_error("Rule not found", HTTPStatus.NOT_FOUND)
                return

            relayed = send_feedback(self.base_dir, rule_id, action)
            self._send_json({"ok": True, "rule_id": rule_id, "status": status, "relayed": relayed})
            return

        # Batch format
        repo = body.get("repo")
        if not repo:
            cwd = body.get("cwd")
            if not cwd:
                self._send_error("Must provide 'cwd' or 'repo'")
                return
            repo = resolve_slug_from_cache(cwd, self.base_dir)
            if not repo:
                self._send_error("Could not resolve repo from cwd")
                return

        accept_nums: list[int] = body.get("accept", [])
        dismiss_nums: list[int] = body.get("dismiss", [])

        if not isinstance(accept_nums, list) or not isinstance(dismiss_nums, list):
            self._send_error("'accept' and 'dismiss' must be lists of numbers")
            return

        all_nums = accept_nums + dismiss_nums
        if not all_nums:
            self._send_json({"ok": True, "accepted": [], "dismissed": []})
            return

        rules_data = load_rules(self.base_dir, repo)
        if rules_data is None:
            self._send_error("No rules found for repo", HTTPStatus.NOT_FOUND)
            return

        resolved = resolve_numbers_to_rules(rules_data, all_nums)
        if resolved is None:
            numbered = number_rules(rules_data)
            valid_nums = {r["num"] for r in numbered}
            invalid = [n for n in all_nums if n not in valid_nums]
            self._send_error(f"Invalid rule numbers: {invalid}")
            return

        by_num = {r["num"]: r for r in resolved}

        accepted_results = []
        dismissed_results = []

        for num in accept_nums:
            r = by_num[num]
            update_rule_status(self.base_dir, repo, r["id"], "accepted")
            try:
                send_feedback(self.base_dir, r["id"], "accept")
            except Exception:
                pass
            accepted_results.append({"num": num, "id": r["id"], "rule": r["rule"]})

        for num in dismiss_nums:
            r = by_num[num]
            update_rule_status(self.base_dir, repo, r["id"], "dismissed")
            try:
                send_feedback(self.base_dir, r["id"], "dismiss")
            except Exception:
                pass
            dismissed_results.append({"num": num, "id": r["id"], "rule": r["rule"]})

        self._send_json({"ok": True, "accepted": accepted_results, "dismissed": dismissed_results})


def create_local_recs_server(
    base_dir: Path | None = None,
    host: str = "127.0.0.1",
    port: int = 19777,
) -> HTTPServer:
    """Create a local recs-only HTTP server. Call server.serve_forever() to run."""
    from qc_trace.server.recs import DEFAULT_BASE_DIR

    if base_dir is None:
        base_dir = DEFAULT_BASE_DIR

    # Create a handler class with base_dir bound
    handler = type("Handler", (LocalRecsHandler,), {"base_dir": base_dir})
    server = HTTPServer((host, port), handler)
    return server
