from __future__ import annotations

import pytest

from app.features.personalized_reco.content_repository.types import ContentProfileDTO
from app.features.personalized_reco.rerank_freqcap.rerank import rerank_and_freqcap
from app.features.personalized_reco.rerank_freqcap.types import ScoredCandidate


def _c(
    *,
    content_id: int,
    score: float,
    author_id: str | None = None,
    template_id: str | None = None,
    stage: str = "general",
    need_key: str = "emotional_support",
    context_key: str = "family",
) -> ScoredCandidate:
    cp = ContentProfileDTO(
        content_id=content_id,
        text="t",
        stage=stage,  # type: ignore[arg-type]
        emotion_score=None,
        context_suitability={context_key: 1.0},
        need_suitability={need_key: 1.0},
        personalization_power=0.0,
        risk_flags=[],
        author_id=author_id,
        template_id=template_id,
    )
    return ScoredCandidate(
        content_id=content_id,
        final_score=score,
        author_id=author_id,
        template_id=template_id,
        content_profile=cp,
    )


def test_dedup_normalizes_str_int_ids() -> None:
    cands = [_c(content_id=1, score=0.9), _c(content_id=2, score=0.8), _c(content_id=3, score=0.7)]
    r = rerank_and_freqcap(
        scene="push",
        scored_candidates=cands,
        already_recommended_ids=["1", "bad"],
        touched_or_viewed_ids=[2],
        k=10,
    )
    got_ids = [x.content_id for x in r.ranked_items]
    assert got_ids == [3]
    assert r.meta.candidate_pool_size_after_dedup == 1
    assert r.meta.freqcap_filtered_counts["sentence"] == 2


def test_freqcap_missing_recent_author_template_is_recorded() -> None:
    cands = [
        _c(content_id=1, score=0.9, author_id="a1", template_id="t1"),
        _c(content_id=2, score=0.8, author_id="a2", template_id="t2"),
    ]
    r = rerank_and_freqcap(
        scene="push",
        scored_candidates=cands,
        already_recommended_ids=[],
        touched_or_viewed_ids=[],
        k=10,
        recent_author_ids=None,
        recent_template_ids=None,
    )
    assert r.meta.missing_history_fields == ["author", "template"]
    assert "author" not in r.meta.freqcap_filtered_counts  # 未提供则不执行该维度
    assert "template" not in r.meta.freqcap_filtered_counts


def test_freqcap_filters_by_recent_author_when_provided() -> None:
    cands = [
        _c(content_id=1, score=0.9, author_id="a1", template_id="t1"),
        _c(content_id=2, score=0.8, author_id="a2", template_id="t2"),
    ]
    r = rerank_and_freqcap(
        scene="widget",
        scored_candidates=cands,
        already_recommended_ids=[],
        touched_or_viewed_ids=[],
        k=10,
        recent_author_ids=["a1"],
        recent_template_ids=None,
    )
    got_ids = [x.content_id for x in r.ranked_items]
    assert got_ids == [2]
    assert r.meta.missing_history_fields == ["template"]
    assert r.meta.freqcap_filtered_counts["author"] == 1


def test_feed_mmr_picks_diverse_second_item() -> None:
    # 构造：Top1 是 a1；c2 分数略高但同作者；c3 分数略低但不同作者/阶段
    c1 = _c(content_id=1, score=1.0, author_id="a1", template_id="t1", stage="general")
    c2 = _c(content_id=2, score=0.99, author_id="a1", template_id="t2", stage="general")
    c3 = _c(content_id=3, score=0.95, author_id="a2", template_id="t3", stage="expecting")

    r = rerank_and_freqcap(
        scene="feed",
        scored_candidates=[c1, c2, c3],
        already_recommended_ids=[],
        touched_or_viewed_ids=[],
        k=2,
    )
    got_ids = [x.content_id for x in r.ranked_items]
    assert got_ids[0] == 1
    assert got_ids[1] == 3


def test_push_topk_sorted_by_score_after_filters() -> None:
    cands = [
        _c(content_id=1, score=0.1),
        _c(content_id=2, score=0.9),
        _c(content_id=3, score=0.8),
    ]
    r = rerank_and_freqcap(
        scene="push",
        scored_candidates=cands,
        already_recommended_ids=[],
        touched_or_viewed_ids=[],
        k=2,
        recent_author_ids=[],
        recent_template_ids=[],
    )
    got_ids = [x.content_id for x in r.ranked_items]
    assert got_ids == [2, 3]

