from __future__ import annotations

import json
import mimetypes
import secrets
from collections.abc import Iterable
from http import HTTPStatus
from http.cookies import SimpleCookie
from typing import Any
from urllib.parse import unquote

from backend.bootstrap.config import (
    DEVICE_COOKIE,
    DEVICE_MAX_AGE,
    SESSION_COOKIE,
    SESSION_MAX_AGE,
    STATIC_DIR,
)
from backend.features.accounts.security import token_hash
from backend.http.context import correlation_id
from backend.http.errors import normalize_error_payload


class HttpTransportMixin:
    """Original HTTP transport, static-file, session and access behavior."""

    application_service: Any
    route_registry: Any

    def cookie_value(self, name: str) -> str:
        cookie = SimpleCookie()
        try:
            cookie.load(self.headers.get("Cookie", ""))
        except Exception:
            return ""
        morsel = cookie.get(name)
        return morsel.value if morsel else ""

    def session_token(self) -> str:
        return self.cookie_value(SESSION_COOKIE)

    def device_token(self) -> str:
        return self.cookie_value(DEVICE_COOKIE)

    def require_auth(self, send_error: bool = True) -> bool:
        raw_token = self.session_token()
        service = self.application_service
        user = service.database.session_user(token_hash(raw_token)) if raw_token else None
        if not user:
            if send_error:
                self.send_json({"error": "请先登录。"}, HTTPStatus.UNAUTHORIZED)
            return False
        self.auth_user = user
        service.bind_user(int(user["id"]))
        return True

    def require_csrf(self) -> bool:
        supplied = self.headers.get("X-CSRF-Token", "")
        expected = str(getattr(self, "auth_user", {}).get("csrf_token") or "")
        if not supplied or not secrets.compare_digest(supplied, expected):
            self.send_json({"error": "请求校验失败，请刷新页面后重试。"}, HTTPStatus.FORBIDDEN)
            return False
        return True

    def require_admin(self) -> bool:
        if str(getattr(self, "auth_user", {}).get("role") or "user") != "admin":
            self.send_json({"error": "需要管理员权限。"}, HTTPStatus.FORBIDDEN)
            return False
        return True

    def require_member(self) -> bool:
        if self.application_service.membership()["active"]:
            return True
        self.send_json(
            {"error": "该功能仅对有效会员开放，请联系管理员开通会员。", "code": "membership_required"},
            HTTPStatus.FORBIDDEN,
        )
        return False

    def require_access(self, method: str, path: str) -> bool:
        route = self.route_registry.resolve(method, path)
        if route is None:
            self.send_json({"error": "Not found"}, HTTPStatus.NOT_FOUND)
            return False
        role = route.access
        if role == "public":
            return True
        if role == "admin":
            return self.require_admin()
        if role == "member":
            return self.require_member()
        return True

    def _cookie_header(self, name: str, value: str, max_age: int) -> str:
        cookie = f"{name}={value}; Path=/; HttpOnly; SameSite=Lax; Max-Age={max_age}"
        if self.headers.get("X-Forwarded-Proto", "").lower() == "https":
            cookie += "; Secure"
        return cookie

    def session_cookie(self, value: str, clear: bool = False) -> str:
        return self._cookie_header(SESSION_COOKIE, value, 0 if clear else SESSION_MAX_AGE)

    def device_cookie(self, value: str, clear: bool = False) -> str:
        return self._cookie_header(DEVICE_COOKIE, value, 0 if clear else DEVICE_MAX_AGE)

    def read_json_body(self, allow_empty: bool = False) -> dict[str, Any]:
        length = int(self.headers.get("Content-Length", "0"))
        if length == 0 and allow_empty:
            return {}
        if length <= 0 or length > 65536:
            raise ValueError("请求内容为空或过大。")
        raw = self.rfile.read(length)
        try:
            payload = json.loads(raw.decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError):
            raise ValueError("请求不是合法 JSON。") from None
        if not isinstance(payload, dict):
            raise ValueError("请求不是合法 JSON。")
        return payload

    def serve_static(self, request_path: str) -> None:
        relative = unquote(request_path).lstrip("/") or "index.html"
        candidate = (STATIC_DIR / relative).resolve()
        try:
            candidate.relative_to(STATIC_DIR.resolve())
        except ValueError:
            self.send_error(HTTPStatus.FORBIDDEN)
            return
        if candidate.is_dir():
            candidate = (candidate / "index.html").resolve()
            try:
                candidate.relative_to(STATIC_DIR.resolve())
            except ValueError:
                self.send_error(HTTPStatus.FORBIDDEN)
                return
        if not candidate.is_file():
            candidate = STATIC_DIR / "index.html"
        try:
            content = candidate.read_bytes()
        except OSError:
            self.send_error(HTTPStatus.NOT_FOUND)
            return
        content_type = mimetypes.guess_type(candidate.name)[0] or "application/octet-stream"
        if content_type.startswith("text/") or content_type in {"application/javascript", "application/json"}:
            content_type += "; charset=utf-8"
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(content)))
        self.send_header("Cache-Control", "no-cache")
        self.end_headers()
        self.wfile.write(content)

    def send_json(
        self,
        payload: dict[str, Any],
        status: HTTPStatus = HTTPStatus.OK,
        headers: dict[str, str] | list[tuple[str, str]] | tuple[tuple[str, str], ...] | None = None,
    ) -> None:
        request_id = getattr(self, "_correlation_id", "")
        if not request_id:
            request_id = correlation_id(self.headers.get("X-Request-ID", ""))
            self._correlation_id = request_id
        payload = normalize_error_payload(payload, status, request_id)
        content = json.dumps(payload, 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(content)))
        self.send_header("Cache-Control", "no-store")
        self.send_header("X-Request-ID", request_id)
        header_items = headers.items() if isinstance(headers, dict) else (headers or ())
        for name, value in header_items:
            self.send_header(name, value)
        self.end_headers()
        self.wfile.write(content)

    def _write_stream_event(self, payload: dict[str, Any]) -> None:
        self.wfile.write(
            (json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n").encode("utf-8")
        )
        self.wfile.flush()

    def send_ndjson_stream(
        self,
        events: Iterable[dict[str, Any]],
        error_types: tuple[type[Exception], ...],
    ) -> None:
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
        self.send_header("Cache-Control", "no-cache, no-transform")
        self.send_header("X-Accel-Buffering", "no")
        self.send_header("Connection", "close")
        self.end_headers()
        try:
            for event in events:
                self._write_stream_event(event)
            self._write_stream_event({"type": "done"})
        except error_types as exc:
            self._write_stream_event({"type": "error", "error": str(exc)})
        except (BrokenPipeError, ConnectionResetError):
            pass
        finally:
            self.close_connection = True

    def log_message(self, format_string: str, *args: Any) -> None:
        print(f"[{self.log_date_time_string()}] {format_string % args}")
