from __future__ import annotations

from typing import Annotated

from fastapi import APIRouter, Path, Query, Request

from backend.data.gateway import MarketDataUnavailable
from backend.data.providers.base import ProviderError
from backend.data.quality import DataQualityError
from backend.features.accounts.auth import (
    AdminWritePrincipal,
    AuthenticatedPrincipal,
    SmartAccessPrincipal,
    SmartWritePrincipal,
)
from backend.features.screener.schemas import (
    CustomStrategyInput,
    FormulaCompileInput,
    FormulaCompileResponse,
    IdentifierResponse,
    MessageResponse,
    ScreenerCatalogResponse,
    ScreenerSyncResponse,
    ScreenerWorkspaceResponse,
    TrackInput,
)
from backend.features.screener.service import ScreenerError
from backend.http.errors import AppError
from backend.llm.gateway import LLMGatewayError

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


@router.get("/catalog", response_model=ScreenerCatalogResponse)
def catalog(request: Request, _principal: AuthenticatedPrincipal) -> dict:
    return request.app.state.container.screener.catalog()


@router.get("", response_model=ScreenerWorkspaceResponse)
def workspace(
    request: Request,
    principal: SmartAccessPrincipal,
    requested_date: Annotated[str, Query(alias="date")],
) -> dict:
    return _call(request, "workspace", requested_date, principal.user.id)


@router.post("/sync", response_model=ScreenerSyncResponse)
def sync(
    request: Request,
    _principal: AdminWritePrincipal,
    requested_date: Annotated[str, Query(alias="date")],
) -> dict:
    return _call(request, "sync_and_run", requested_date)


@router.put("/custom", response_model=dict)
def save_custom(
    payload: CustomStrategyInput,
    request: Request,
    principal: SmartWritePrincipal,
) -> dict:
    return _call(request, "save_custom", principal.user.id, payload.name, payload.formula)


@router.post("/formula/compile", response_model=FormulaCompileResponse)
def compile_formula(
    payload: FormulaCompileInput,
    request: Request,
    principal: SmartWritePrincipal,
) -> dict:
    return _call(request, "compile_formula", principal, payload.description)


@router.delete("/custom/{strategy_id}", response_model=MessageResponse)
def delete_custom(
    request: Request,
    principal: SmartWritePrincipal,
    strategy_id: Annotated[int, Path(gt=0)],
) -> MessageResponse:
    _call(request, "delete_custom", principal.user.id, strategy_id)
    return MessageResponse(message="自定义策略已删除。")


@router.post("/custom/{strategy_id}/run", response_model=dict)
def run_custom(
    request: Request,
    principal: SmartWritePrincipal,
    strategy_id: Annotated[int, Path(gt=0)],
    requested_date: Annotated[str, Query(alias="date")],
) -> dict:
    return _call(request, "run_custom", principal.user.id, strategy_id, requested_date)


@router.get("/tracks", response_model=list[dict])
def tracks(request: Request, principal: SmartAccessPrincipal) -> list[dict]:
    return _call(request, "tracks", principal.user.id)


@router.post("/tracks", response_model=IdentifierResponse)
def add_track(
    payload: TrackInput,
    request: Request,
    principal: SmartWritePrincipal,
) -> IdentifierResponse:
    identifier = _call(request, "add_track", principal.user.id, payload.run_id, payload.identifier)
    return IdentifierResponse(id=identifier)


@router.delete("/tracks/{track_id}", response_model=MessageResponse)
def remove_track(
    request: Request,
    principal: SmartWritePrincipal,
    track_id: Annotated[int, Path(gt=0)],
) -> MessageResponse:
    _call(request, "remove_track", principal.user.id, track_id)
    return MessageResponse(message="已停止跟踪。")


def _call(request: Request, method: str, *args):
    try:
        return getattr(request.app.state.container.screener, method)(*args)
    except (ScreenerError, MarketDataUnavailable, ProviderError, DataQualityError) as exc:
        raise AppError("screener_unavailable", str(exc), 409) 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
