from __future__ import annotations

from collections import defaultdict
from typing import Any, Iterable, Optional

from app.features.personalized_reco.content_repository.types import ContentProfileDTO
from app.features.personalized_reco.reco_engine.types import HardFilterResult, RecoConstraints, Scene


def _user_stage_key(user_profile: object) -> str:
    """
    从 user_profile.stage(one-hot) 提取用户阶段。
    约定：unknown 通常必填，但这里做防御。
    """

    stage_obj = getattr(user_profile, "stage", None)
    if stage_obj is None:
        return "unknown"
    if getattr(stage_obj, "expecting", 0) == 1:
        return "expecting"
    if getattr(stage_obj, "parenting", 0) == 1:
        return "parenting"
    return "unknown"


def _user_emotion_score(user_profile: object) -> Optional[float]:
    v = getattr(user_profile, "emotion_score", None)
    if v is None:
        return None
    try:
        f = float(v)
    except Exception:
        return None
    if f != f:
        return None
    return f


def _count_hits(counter: dict[str, int], hits: Iterable[str]) -> None:
    for h in hits:
        counter[str(h)] += 1


def hard_filter(
    *,
    scene: Scene,
    user_profile: object,
    candidates: list[ContentProfileDTO],
    constraints: Optional[RecoConstraints] = None,
) -> HardFilterResult:
    """
    Hard Filter（硬过滤）。

    V1：仅实现硬规则集合（不做软惩罚，不做扩展 hard_rules）。
    """

    cons = constraints or RecoConstraints()

    exclude_author_ids = set([a for a in (cons.exclude_author_ids or []) if a is not None and str(a).strip() != ""])
    exclude_template_ids = set([t for t in (cons.exclude_template_ids or []) if t is not None and str(t).strip() != ""])
    exclude_content_ids = set([int(x) for x in (cons.exclude_content_ids or []) if x is not None])

    u_stage = _user_stage_key(user_profile)
    u_emotion = _user_emotion_score(user_profile)
    emotion_low = u_emotion is not None and float(u_emotion) <= 0.2

    kept: list[ContentProfileDTO] = []
    removed_count = 0

    # 统计：按命中 key 聚合计数（risk_flags 直接用 flag 字符串；跨维度/约束用 rule:* / constraint:* 前缀）
    hit_counts: dict[str, int] = defaultdict(int)
    hits_by_content_id: dict[int, list[str]] = {}

    for c in candidates or []:
        cid = int(c.content_id)
        hits: list[str] = []

        # 约束：按 content_id/author_id/template_id 排除（视为硬过滤）
        if cid in exclude_content_ids:
            hits.append("constraint:exclude_content_id")
        if c.author_id and c.author_id in exclude_author_ids:
            hits.append("constraint:exclude_author_id")
        if c.template_id and c.template_id in exclude_template_ids:
            hits.append("constraint:exclude_template_id")

        flags = set([str(x) for x in (c.risk_flags or []) if x is not None and str(x).strip() != ""])

        # 全场景必挡
        if "block_health_medical" in flags:
            hits.append("block_health_medical")

        # 与用户阶段相关
        if u_stage == "unknown" and "unsafe_for_stage_unknown" in flags:
            hits.append("unsafe_for_stage_unknown")
        if u_stage == "parenting" and "unsafe_for_stage_parenting" in flags:
            hits.append("unsafe_for_stage_parenting")

        # 与用户情绪相关
        if emotion_low and "unsafe_for_emotion_low" in flags:
            hits.append("unsafe_for_emotion_low")

        # 跨维度规则：unknown stage + parenting_pressure 强命中 + 高个性化
        if u_stage == "unknown":
            try:
                need_val = float(c.need_suitability.get("parenting_pressure", 0.0))
            except Exception:
                need_val = 0.0
            if need_val >= 1.0 and float(getattr(c, "personalization_power", 0.0)) >= 1.0:
                hits.append("rule:unknown_stage_parenting_pressure_power1")

        if hits:
            removed_count += 1
            # 单条去重后再计数，避免同 key 重复
            uniq_hits = sorted(set(hits))
            hits_by_content_id[cid] = uniq_hits
            _count_hits(hit_counts, uniq_hits)
            continue

        hits_by_content_id[cid] = []
        kept.append(c)

    return HardFilterResult(
        kept_items=kept,
        removed_count=int(removed_count),
        risk_filtered_count_by_flag=dict(hit_counts),
        hits_by_content_id=hits_by_content_id,
    )

