from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Optional

from fastapi import APIRouter, Depends, Header
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.limits import rate_limit_reco_by_ip
from app.db.session import get_db
from app.features.personalized_reco.content_repository.interface import ContentRepository
from app.features.personalized_reco.content_repository.sqlalchemy_repo import SqlAlchemyContentRepository
from app.features.personalized_reco.reco_engine import recommend
from app.features.personalized_reco.reco_engine.types import RecoConstraints, RecoEngineResult
from app.features.user_profile_scoring.types import UserProfileV1_2

router = APIRouter(
    prefix="/v1/reco",
    tags=["reco"],
    dependencies=[Depends(rate_limit_reco_by_ip)],
)


class RecoRequest(BaseModel):
    k: Optional[int] = None
    user_profile: UserProfileV1_2
    already_recommended_ids: list[Any] = Field(default_factory=list)
    touched_or_viewed_ids: list[Any] = Field(default_factory=list)
    now: Optional[datetime] = None


def _parse_now_from_header(x_now: Optional[str]) -> Optional[datetime]:
    if not x_now:
        return None
    raw = str(x_now).strip()
    if not raw:
        return None
    # 支持 Z
    if raw.endswith("Z"):
        raw = raw[:-1] + "+00:00"
    try:
        dt = datetime.fromisoformat(raw)
    except Exception:
        return None
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    return dt


def _pick_now(*, header_now: Optional[str], body_now: Optional[datetime]) -> datetime:
    dt = _parse_now_from_header(header_now)
    if dt is not None:
        return dt
    if body_now is not None:
        if body_now.tzinfo is None:
            return body_now.replace(tzinfo=timezone.utc)
        return body_now
    return datetime.now(timezone.utc)


def _pick_locale_from_accept_language(accept_language: Optional[str]) -> str:
    """
    从 Accept-Language 映射 locale：
    - 缺失/空 -> en
    - 含 zh-TW/zh-HK/tc -> tc
    - 其他 -> en
    """

    raw = (accept_language or "").strip().lower()
    if not raw:
        return "en"
    if "zh-tw" in raw or "zh-hk" in raw or "tc" in raw:
        return "tc"
    return "en"


async def get_reco_repo(db: AsyncSession = Depends(get_db)) -> ContentRepository:
    """
    构造推荐 repo（可在测试中 override，避免依赖真实 DB）。
    """

    return SqlAlchemyContentRepository(db)


@router.post("/feed", response_model=RecoEngineResult)
async def reco_feed(
    req: RecoRequest,
    repo: ContentRepository = Depends(get_reco_repo),
    x_now: Optional[str] = Header(default=None, alias="X-Now"),
    accept_language: Optional[str] = Header(default=None, alias="Accept-Language"),
) -> RecoEngineResult:
    k_i = 30 if req.k is None else int(req.k)
    now = _pick_now(header_now=x_now, body_now=req.now)
    locale = _pick_locale_from_accept_language(accept_language)

    return await recommend(
        repo=repo,
        scene="feed",
        user_profile=req.user_profile,
        already_recommended_ids=list(req.already_recommended_ids or []),
        touched_or_viewed_ids=list(req.touched_or_viewed_ids or []),
        k=k_i,
        now=now,
        locale=locale,
        constraints=RecoConstraints(),
    )


@router.post("/push", response_model=RecoEngineResult)
async def reco_push(
    req: RecoRequest,
    repo: ContentRepository = Depends(get_reco_repo),
    x_now: Optional[str] = Header(default=None, alias="X-Now"),
    accept_language: Optional[str] = Header(default=None, alias="Accept-Language"),
) -> RecoEngineResult:
    k_i = 1 if req.k is None else int(req.k)
    now = _pick_now(header_now=x_now, body_now=req.now)
    locale = _pick_locale_from_accept_language(accept_language)

    return await recommend(
        repo=repo,
        scene="push",
        user_profile=req.user_profile,
        already_recommended_ids=list(req.already_recommended_ids or []),
        touched_or_viewed_ids=list(req.touched_or_viewed_ids or []),
        k=k_i,
        now=now,
        locale=locale,
        constraints=RecoConstraints(),
    )


@router.post("/widget", response_model=RecoEngineResult)
async def reco_widget(
    req: RecoRequest,
    repo: ContentRepository = Depends(get_reco_repo),
    x_now: Optional[str] = Header(default=None, alias="X-Now"),
    accept_language: Optional[str] = Header(default=None, alias="Accept-Language"),
) -> RecoEngineResult:
    k_i = 1 if req.k is None else int(req.k)
    now = _pick_now(header_now=x_now, body_now=req.now)
    locale = _pick_locale_from_accept_language(accept_language)

    return await recommend(
        repo=repo,
        scene="widget",
        user_profile=req.user_profile,
        already_recommended_ids=list(req.already_recommended_ids or []),
        touched_or_viewed_ids=list(req.touched_or_viewed_ids or []),
        k=k_i,
        now=now,
        locale=locale,
        constraints=RecoConstraints(),
    )

