"""
category_tree_weight 四维热度全局排名归一化打分。

在 category_tree_weight 全部节点写入完成后执行：
1. 对 ext_pop / plat_sust_pop / plat_ly_pop / recent_pop 各自独立做全局排名
2. 将排名映射为 [1/n, 1] 区间的归一化分（count>0 的节点参与排名，其余为 null）
3. 四维分相加写入 total_score（无有效维度时为 null）
"""
from __future__ import annotations

import logging
from decimal import Decimal
from typing import Any

from supply_agent.ranking import rank_to_scores
from supply_infra.db.repositories.category_tree_weight_repo import (
    CategoryTreeWeightRepository,
)
from supply_infra.db.session import get_session

logger = logging.getLogger(__name__)

POP_DIM_KEYS: tuple[str, ...] = (
    "ext_pop",
    "plat_sust_pop",
    "plat_ly_pop",
    "recent_pop",
)

_UPDATE_BATCH = 500


def _dec(value: float, places: int = 8) -> Decimal:
    return Decimal(str(round(float(value), places)))


def _optional_dec(value: float | None, places: int = 8) -> Decimal | None:
    if value is None:
        return None
    return _dec(value, places)


def _materialize_weight_row(row: Any) -> dict[str, Any]:
    """在 session 内抽出标量，避免 DetachedInstanceError。"""
    payload: dict[str, Any] = {
        "category_id": int(row.category_id),
        "biz_dt": str(row.biz_dt),
    }
    for dim in POP_DIM_KEYS:
        count = int(getattr(row, f"{dim}_count", 0) or 0)
        payload[f"{dim}_count"] = count
        if count > 0:
            avg_raw = getattr(row, f"{dim}_avg", None)
            payload[f"{dim}_avg"] = float(avg_raw) if avg_raw is not None else None
        else:
            payload[f"{dim}_avg"] = None
    return payload


def _build_rank_score_rows(
    rows: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    """根据已写入的权重行计算各节点排名分。"""
    dim_scores: dict[str, dict[int, float]] = {}
    for dim in POP_DIM_KEYS:
        candidates: list[tuple[int, float]] = []
        for row in rows:
            count = int(row[f"{dim}_count"])
            if count <= 0:
                continue
            avg_raw = row[f"{dim}_avg"]
            if avg_raw is None:
                continue
            avg = float(avg_raw)
            candidates.append((int(row["category_id"]), avg))
        dim_scores[dim] = rank_to_scores(candidates)

    updates: list[dict[str, Any]] = []
    for row in rows:
        category_id = int(row["category_id"])
        biz_dt = str(row["biz_dt"])
        score_values = {
            f"{dim}_score": dim_scores[dim].get(category_id)
            for dim in POP_DIM_KEYS
        }
        non_null_scores = [v for v in score_values.values() if v is not None]
        total = sum(non_null_scores) if non_null_scores else None
        updates.append(
            {
                "category_id": category_id,
                "biz_dt": biz_dt,
                **{
                    col: _optional_dec(score_values[col])
                    for col in (
                        "ext_pop_score",
                        "plat_sust_pop_score",
                        "plat_ly_pop_score",
                        "recent_pop_score",
                    )
                },
                "total_score": _optional_dec(total),
            }
        )
    return updates


def update_category_tree_rank_scores(biz_dt: str) -> dict[str, Any]:
    """在 category_tree_weight 全部写入后，按 biz_dt 更新四维排名分与 total_score。"""
    with get_session() as session:
        repo = CategoryTreeWeightRepository(session)
        rows = [
            _materialize_weight_row(row)
            for row in repo.list_by_biz_dt(biz_dt)
        ]

    if not rows:
        logger.info("Category tree rank scores: no rows, skip biz_dt=%s", biz_dt)
        return {"biz_dt": biz_dt, "nodes": 0, "updated": 0}

    updates = _build_rank_score_rows(rows)
    updated = 0
    with get_session() as session:
        repo = CategoryTreeWeightRepository(session)
        for i in range(0, len(updates), _UPDATE_BATCH):
            updated += repo.update_rank_scores(updates[i : i + _UPDATE_BATCH])

    result = {
        "biz_dt": biz_dt,
        "nodes": len(updates),
        "updated": updated,
    }
    logger.info("Category tree rank scores completed: %s", result)
    return result
