from __future__ import annotations

from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import mimetypes
import os
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

from .service import DemoService, DemoServiceError
from .solar_client import SolarClient, SolarClientError


ROOT = Path(__file__).resolve().parent
STATIC_ROOT = ROOT / "static"
MAX_BODY_BYTES = 1_000_000


class DemoHandler(BaseHTTPRequestHandler):
    service: DemoService

    def _security_headers(self) -> None:
        self.send_header("X-Content-Type-Options", "nosniff")
        self.send_header("X-Frame-Options", "DENY")
        self.send_header("Referrer-Policy", "no-referrer")
        self.send_header(
            "Content-Security-Policy",
            "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'",
        )
        self.send_header("Cache-Control", "no-store")

    def _json(self, status: int, value: dict[str, Any]) -> None:
        body = json.dumps(value, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self._security_headers()
        self.end_headers()
        self.wfile.write(body)

    def _read_json(self) -> dict[str, Any]:
        try:
            length = int(self.headers.get("Content-Length", "0"))
        except ValueError as error:
            raise DemoServiceError("invalid content length") from error
        if length <= 0 or length > MAX_BODY_BYTES:
            raise DemoServiceError("request body size is invalid")
        try:
            value = json.loads(self.rfile.read(length).decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError) as error:
            raise DemoServiceError("request body must be UTF-8 JSON") from error
        if not isinstance(value, dict):
            raise DemoServiceError("request body must be an object")
        return value

    def _serve_static(self, relative: str) -> None:
        relative = "index.html" if relative in {"", "/"} else relative.lstrip("/")
        candidate = (STATIC_ROOT / relative).resolve()
        if STATIC_ROOT.resolve() not in candidate.parents and candidate != STATIC_ROOT.resolve():
            self.send_error(HTTPStatus.NOT_FOUND)
            return
        if not candidate.is_file():
            self.send_error(HTTPStatus.NOT_FOUND)
            return
        body = candidate.read_bytes()
        content_type = mimetypes.guess_type(candidate.name)[0] or "application/octet-stream"
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", f"{content_type}; charset=utf-8" if content_type.startswith("text/") else content_type)
        self.send_header("Content-Length", str(len(body)))
        self._security_headers()
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self) -> None:  # noqa: N802
        path = urlparse(self.path).path
        try:
            if path == "/api/status":
                self._json(HTTPStatus.OK, {"ok": True, "data": self.service.status()})
            elif path.startswith("/api/cases/"):
                case_id = path.rsplit("/", 1)[-1]
                self._json(HTTPStatus.OK, {"ok": True, "data": self.service.get_case(case_id)})
            else:
                self._serve_static(path)
        except (DemoServiceError, SolarClientError, ValueError, KeyError) as error:
            self._json(HTTPStatus.BAD_REQUEST, {"ok": False, "error": str(error)})
        except Exception as error:
            self._json(HTTPStatus.INTERNAL_SERVER_ERROR, {"ok": False, "error": f"internal error: {type(error).__name__}"})

    def do_POST(self) -> None:  # noqa: N802
        path = urlparse(self.path).path
        try:
            payload = self._read_json()
            if path == "/api/analyze":
                if set(payload) != {"document_text", "fault_mode"}:
                    raise DemoServiceError("analyze fields do not match the strict schema")
                data = self.service.analyze(payload["document_text"], payload["fault_mode"])
            elif path == "/api/approve":
                if set(payload) != {"case_id"}:
                    raise DemoServiceError("approve fields do not match the strict schema")
                data = self.service.approve(payload["case_id"])
            elif path == "/api/replay":
                if set(payload) != {"case_id"}:
                    raise DemoServiceError("replay fields do not match the strict schema")
                data = self.service.replay(payload["case_id"])
            else:
                self._json(HTTPStatus.NOT_FOUND, {"ok": False, "error": "not found"})
                return
            self._json(HTTPStatus.OK, {"ok": True, "data": data})
        except (DemoServiceError, SolarClientError, ValueError, KeyError, TypeError) as error:
            self._json(HTTPStatus.BAD_REQUEST, {"ok": False, "error": str(error)})
        except Exception as error:
            self._json(HTTPStatus.INTERNAL_SERVER_ERROR, {"ok": False, "error": f"internal error: {type(error).__name__}"})

    def log_message(self, format: str, *args: Any) -> None:
        if os.environ.get("DEMO_HTTP_LOG", "0") == "1":
            super().log_message(format, *args)


def build_server(
    host: str | None = None,
    port: int | None = None,
    data_dir: Path | None = None,
    solar: SolarClient | None = None,
) -> ThreadingHTTPServer:
    service = DemoService(
        data_dir or Path(os.environ.get("DEMO_DATA_DIR", ".demo-data")),
        solar or SolarClient.from_environment(),
    )
    handler = type("BoundDemoHandler", (DemoHandler,), {"service": service})
    bind_host = host if host is not None else os.environ.get("DEMO_HOST", "127.0.0.1")
    bind_port = port if port is not None else int(os.environ.get("DEMO_PORT", "8765"))
    return ThreadingHTTPServer((bind_host, bind_port), handler)


def main() -> None:
    server = build_server()
    host, port = server.server_address[:2]
    print(f"Agent Reliability Demo: http://{host}:{port}")
    print("Live Solar mode" if server.RequestHandlerClass.service.solar.mode == "live" else "Sample mode (set UPSTAGE_API_KEY for live Solar)")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()


if __name__ == "__main__":
    main()
