from __future__ import annotations

import json
from collections.abc import Iterator
from typing import Annotated, Literal

from fastapi import APIRouter, Query, Request
from fastapi.responses import StreamingResponse

from backend.data.gateway import MarketDataUnavailable
from backend.features.accounts.auth import SmartAccessPrincipal, SmartWritePrincipal
from backend.features.heaven.schemas import (
    DeleteResponse,
    FortuneInput,
    HeartCompleteInput,
    HeartLineInput,
    InterpretInput,
    TrendInput,
)
from backend.features.heaven.service import HeavenError
from backend.http.errors import AppError
from backend.llm.gateway import LLMGatewayError

router = APIRouter(prefix="/heaven", tags=["heaven"])


@router.get("/setup", response_model=dict)
def setup(
    request: Request,
    principal: SmartAccessPrincipal,
    reading_date: Annotated[str, Query(alias="date", min_length=10, max_length=10)],
) -> dict:
    return _call(request, "setup", principal, reading_date)


@router.post("/trend/load", response_model=dict)
def load_trend(payload: TrendInput, request: Request, principal: SmartWritePrincipal) -> dict:
    return _call(request, "trend", principal, payload.query, payload.trade_date, payload.manual)


@router.post("/fortune", response_model=dict)
def create_fortune(payload: FortuneInput, request: Request, principal: SmartWritePrincipal) -> dict:
    return _call(request, "fortune", principal, payload.trade_date)


@router.post("/heart/line", response_model=dict)
def heart_line(payload: HeartLineInput, request: Request, principal: SmartWritePrincipal) -> dict:
    return _call(request, "heart_line", principal, payload.trade_date, payload.values)


@router.post("/heart/complete", response_model=dict)
def complete_heart(
    payload: HeartCompleteInput, request: Request, principal: SmartWritePrincipal
) -> dict:
    return _call(
        request,
        "complete_heart",
        principal,
        payload.trade_date,
        payload.values,
        payload.first_thought_confirmed,
    )


@router.get("/readings", response_model=list[dict])
def readings(
    request: Request,
    principal: SmartAccessPrincipal,
    mode: Annotated[Literal["trend", "fortune", "heart"] | None, Query()] = None,
    reading_date: Annotated[str | None, Query(alias="date")] = None,
) -> list[dict]:
    return _call(request, "readings", principal, mode, reading_date)


@router.delete("/readings/{reading_id}", response_model=DeleteResponse)
def delete_reading(
    reading_id: int, request: Request, principal: SmartWritePrincipal
) -> DeleteResponse:
    return DeleteResponse(deleted=_call(request, "delete", principal, reading_id))


@router.post("/interpret")
def interpret(
    payload: InterpretInput, request: Request, principal: SmartWritePrincipal
) -> StreamingResponse:
    prepared = _call(request, "prepare_interpret", principal, payload.reading_id)

    def body() -> Iterator[bytes]:
        for event in request.app.state.container.heaven.stream_interpret(prepared):
            yield (json.dumps(event, ensure_ascii=False, separators=(",", ":")) + "\n").encode(
                "utf-8"
            )

    return StreamingResponse(
        body(),
        media_type="application/x-ndjson",
        headers={"Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no"},
    )


def _call(request: Request, method: str, *args):
    try:
        return getattr(request.app.state.container.heaven, method)(*args)
    except HeavenError as exc:
        raise AppError("heaven_unavailable", str(exc), 409) from exc
    except MarketDataUnavailable as exc:
        raise AppError("market_data_unavailable", str(exc), 503) from exc
    except LLMGatewayError as exc:
        status = 403 if exc.code in {"membership_required", "quota_exhausted"} else 503
        raise AppError(exc.code, str(exc), status) from exc
