from __future__ import annotations

import importlib
from datetime import datetime, timezone
from typing import Any

import pytest
from fastapi.testclient import TestClient


def _set_min_env(monkeypatch: pytest.MonkeyPatch) -> None:
    # 让 Settings 可构造（引擎不会在单测中真正连接 DB/Redis）
    monkeypatch.setenv(
        "DATABASE_URL",
        "mysql+aiomysql://u:p@127.0.0.1:3306/mindfulness_dev_test?charset=utf8mb4",
    )
    monkeypatch.setenv("REDIS_URL", "redis://127.0.0.1:6379/0")
    monkeypatch.setenv("CELERY_BROKER_URL", "redis://127.0.0.1:6379/0")


def _user_profile_dict() -> dict[str, Any]:
    return {
        "profile_version": "v1.2",
        "profile_source": "questionnaire",
        "profile_generated_at": "2026-02-02T12:00:00Z",
        "profile_confidence": 1.0,
        "profile_answered": {"stage": True, "emotion": False, "context": False, "need": False},
        "stage": {"unknown": 1},
        "emotion_score": None,
        "context": {},
        "need": {},
    }


@pytest.fixture
def client(monkeypatch: pytest.MonkeyPatch):
    _set_min_env(monkeypatch)

    # 清理 settings cache，避免被其他测试污染
    from app.core import config as config_mod

    config_mod.get_settings.cache_clear()

    # 重新加载 main，确保使用最新 env
    import app.main as main_mod

    importlib.reload(main_mod)

    app = main_mod.create_app()

    # override repo（避免依赖真实 DB）
    from app.api.v1 import reco as reco_mod
    from app.features.personalized_reco.content_repository.types import ContentProfileDTO

    class _FakeRepo:
        async def fetch_candidates(self, **kwargs):  # type: ignore[no-untyped-def]
            # 返回一条可下发内容
            return [
                ContentProfileDTO(
                    content_id=1,
                    text="t1",
                    stage="general",
                    emotion_score=None,
                    context_suitability={},
                    need_suitability={},
                    personalization_power=0.0,
                    risk_flags=[],
                    author_id=None,
                    template_id=None,
                    review_confidence=0.7,
                )
            ]

        async def fetch_contents_by_ids(self, **kwargs):  # type: ignore[no-untyped-def]
            return []

    async def _override_repo():  # type: ignore[no-untyped-def]
        return _FakeRepo()

    app.dependency_overrides[reco_mod.get_reco_repo] = _override_repo

    # 清空限流计数，避免跨测试污染
    import app.api.limits as limits_mod

    limits_mod._reco_rate_limiter._counters.clear()  # type: ignore[attr-defined]
    limits_mod._reco_rate_limiter._last_gc_bucket = 0  # type: ignore[attr-defined]

    return TestClient(app)


def test_accept_language_mapping_to_tc(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
    # 固定时间，避免跨分钟 flake
    import app.api.limits as limits_mod

    monkeypatch.setattr(limits_mod.time, "time", lambda: 1738497600.0)  # 2025-02-02 12:00:00Z 的某个时间戳

    resp = client.post(
        "/v1/reco/feed",
        json={"user_profile": _user_profile_dict(), "already_recommended_ids": [], "touched_or_viewed_ids": []},
        headers={"Accept-Language": "zh-TW,zh;q=0.9"},
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["meta"]["config_snapshot"]["locale"] == "tc"


def test_x_now_header_priority_over_body_now(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
    import app.api.limits as limits_mod

    monkeypatch.setattr(limits_mod.time, "time", lambda: 1738497600.0)

    resp = client.post(
        "/v1/reco/push",
        json={
            "user_profile": _user_profile_dict(),
            "now": "2026-02-01T00:00:00Z",
            "already_recommended_ids": [],
            "touched_or_viewed_ids": [],
        },
        headers={"X-Now": "2026-02-02T12:00:00Z", "Accept-Language": "en-US"},
    )
    assert resp.status_code == 200
    data = resp.json()
    # meta.config_snapshot 里没有 now，但 served_k 应该正常
    assert data["meta"]["served_k"] == 1


def test_rate_limit_10_per_minute(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
    import app.api.limits as limits_mod

    monkeypatch.setattr(limits_mod.time, "time", lambda: 1738497600.0)

    body = {"user_profile": _user_profile_dict(), "already_recommended_ids": [], "touched_or_viewed_ids": []}
    for _ in range(10):
        r = client.post("/v1/reco/widget", json=body)
        assert r.status_code == 200

    r = client.post("/v1/reco/widget", json=body)
    assert r.status_code == 429
    assert r.json()["detail"] == "rate_limited"


def test_celery_tasks_can_call_generate(monkeypatch: pytest.MonkeyPatch) -> None:
    _set_min_env(monkeypatch)
    from app.core import config as config_mod

    config_mod.get_settings.cache_clear()

    import app.tasks.reco as reco_tasks

    # monkeypatch async runner，避免依赖 DB
    async def _fake_run_reco_async(**kwargs):  # type: ignore[no-untyped-def]
        from app.features.personalized_reco.observability.types import RecoMeta
        from app.features.personalized_reco.reco_engine.types import RecoEngineResult, RecommendedItem

        return RecoEngineResult(
            items=[RecommendedItem(content_id=1, text="t1", final_score=1.0, fallback_level_final=0, explanations={})],
            meta=RecoMeta(scene="push", served_k=1),
        )

    monkeypatch.setattr(reco_tasks, "_run_reco_async", _fake_run_reco_async)

    out = reco_tasks.generate(scene="push", user_profile=_user_profile_dict(), k=1)
    assert out["meta"]["served_k"] == 1

