from __future__ import annotations

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

from celery import shared_task

from app.db.session import AsyncSessionLocal
from app.features.personalized_reco.content_repository.sqlalchemy_repo import SqlAlchemyContentRepository
from app.features.personalized_reco.content_repository.types import normalize_locale
from app.features.personalized_reco.reco_engine import recommend
from app.features.personalized_reco.reco_engine.types import RecoConstraints, RecoEngineResult, Scene
from app.features.user_profile_scoring.types import UserProfileV1_2


def _ensure_now(now: Optional[datetime]) -> datetime:
    if now is None:
        return datetime.now(timezone.utc)
    if now.tzinfo is None:
        return now.replace(tzinfo=timezone.utc)
    return now


def _ensure_locale(locale: Optional[str]) -> str:
    raw = (locale or "").strip() or "en"
    # 严格校验只支持 en/tc（允许 en-US 等在 normalize_locale 内归一化）
    return str(normalize_locale(raw))


async def _run_reco_async(
    *,
    scene: Scene,
    user_profile: UserProfileV1_2,
    already_recommended_ids: list[Any],
    touched_or_viewed_ids: list[Any],
    k: int,
    now: datetime,
    locale: str,
) -> RecoEngineResult:
    async with AsyncSessionLocal() as session:
        repo = SqlAlchemyContentRepository(session)
        return await recommend(
            repo=repo,
            scene=scene,
            user_profile=user_profile,
            already_recommended_ids=list(already_recommended_ids or []),
            touched_or_viewed_ids=list(touched_or_viewed_ids or []),
            k=int(k),
            now=now,
            locale=locale,
            constraints=RecoConstraints(),
        )


def _run_reco_sync(
    *,
    scene: Scene,
    user_profile: UserProfileV1_2,
    already_recommended_ids: list[Any],
    touched_or_viewed_ids: list[Any],
    k: int,
    now: Optional[datetime],
    locale: Optional[str],
) -> dict[str, Any]:
    effective_now = _ensure_now(now)
    effective_locale = _ensure_locale(locale)
    result = asyncio.run(
        _run_reco_async(
            scene=scene,
            user_profile=user_profile,
            already_recommended_ids=already_recommended_ids,
            touched_or_viewed_ids=touched_or_viewed_ids,
            k=int(k),
            now=effective_now,
            locale=effective_locale,
        )
    )
    # 默认不存结果，但返回值可用于开发调试（worker 通常 ignore_result）
    return result.model_dump()


@shared_task(name="tasks.reco.generate")
def generate(
    *,
    scene: Scene,
    user_profile: dict[str, Any],
    already_recommended_ids: Optional[list[Any]] = None,
    touched_or_viewed_ids: Optional[list[Any]] = None,
    k: Optional[int] = None,
    now: Optional[str] = None,
    locale: Optional[str] = None,
) -> dict[str, Any]:
    """
    推荐生成任务（通用入口）。

    说明：
    - 入参尽量保持小（避免 Redis 队列膨胀）
    - 默认 worker 配置为 ignore_result，但这里仍返回结构，便于本地调试
    """

    # 解析 user_profile（严格按 V1.2）
    u = UserProfileV1_2.model_validate(user_profile or {})

    # k 默认按场景（与 API 一致）
    if k is None:
        k_i = 30 if scene == "feed" else 1
    else:
        k_i = int(k)

    # now 支持 ISO 字符串
    dt: Optional[datetime]
    if not now:
        dt = None
    else:
        raw = str(now).strip()
        if raw.endswith("Z"):
            raw = raw[:-1] + "+00:00"
        try:
            dt = datetime.fromisoformat(raw)
        except Exception:
            dt = None

    return _run_reco_sync(
        scene=scene,
        user_profile=u,
        already_recommended_ids=list(already_recommended_ids or []),
        touched_or_viewed_ids=list(touched_or_viewed_ids or []),
        k=k_i,
        now=dt,
        locale=locale,
    )


def _deliver_push_placeholder(payload: dict[str, Any]) -> None:
    """
    Push 下游写入占位函数（V1 不接真实推送系统）。
    """

    _ = payload
    return None


@shared_task(name="tasks.reco.push_once")
def push_once(
    *,
    user_profile: dict[str, Any],
    already_recommended_ids: Optional[list[Any]] = None,
    touched_or_viewed_ids: Optional[list[Any]] = None,
    now: Optional[str] = None,
    locale: Optional[str] = None,
) -> dict[str, Any]:
    """
    单次 Push 生成（占位任务）。
    """

    payload = generate(
        scene="push",
        user_profile=user_profile,
        already_recommended_ids=already_recommended_ids,
        touched_or_viewed_ids=touched_or_viewed_ids,
        k=1,
        now=now,
        locale=locale,
    )
    _deliver_push_placeholder(payload)
    return payload

