from __future__ import annotations

import json
from typing import Any


def build_popularity(
    trade_date: str,
    ths_rows: tuple[dict[str, Any], ...],
    dc_rows: tuple[dict[str, Any], ...],
    previous_ths: tuple[dict[str, Any], ...],
    previous_dc: tuple[dict[str, Any], ...],
) -> dict[str, Any]:
    ths = _normalize(ths_rows, "热股", previous_ths)
    dc = _normalize(dc_rows, "A股市场", previous_dc)
    ths_map = {str(item["identifier"]): item for item in ths}
    dc_map = {str(item["identifier"]): item for item in dc}
    combined = []
    for identifier in set(ths_map) | set(dc_map):
        ths_item = ths_map.get(identifier)
        dc_item = dc_map.get(identifier)
        base = ths_item or dc_item or {}
        ths_rank = int(ths_item["rank"]) if ths_item else None
        dc_rank = int(dc_item["rank"]) if dc_item else None
        score = (101 - (ths_rank or 101)) * 0.5 + (201 - (dc_rank or 201)) * 0.25
        combined.append(
            {
                **base,
                "ths_rank": ths_rank,
                "dc_rank": dc_rank,
                "score": round(score, 2),
                "dual_source": bool(ths_item and dc_item),
                "concepts": list((ths_item or {}).get("concepts") or []),
            }
        )
    combined.sort(
        key=lambda item: (bool(item["dual_source"]), float(item["score"])), reverse=True
    )
    for rank, item in enumerate(combined, start=1):
        item["rank"] = rank
    return {
        "trade_date": trade_date,
        "summary": {
            "ths_count": len(ths),
            "dc_count": len(dc),
            "dual_count": sum(bool(item["dual_source"]) for item in combined),
        },
        "combined": combined[:200],
        "ths": ths,
        "dc": dc,
    }


def _normalize(
    rows: tuple[dict[str, Any], ...],
    data_type: str,
    previous_rows: tuple[dict[str, Any], ...],
) -> list[dict[str, Any]]:
    previous = {
        str(row.get("ts_code") or ""): int(_number(row.get("rank")))
        for row in previous_rows
        if str(row.get("data_type") or "") == data_type
    }
    items = []
    for row in rows:
        if str(row.get("data_type") or "") != data_type:
            continue
        identifier = str(row.get("ts_code") or "")
        rank = int(_number(row.get("rank")))
        if not identifier or rank <= 0:
            continue
        prior = previous.get(identifier)
        items.append(
            {
                "rank": rank,
                "identifier": identifier,
                "code": identifier.split(".")[0],
                "name": str(row.get("ts_name") or ""),
                "change": round(_number(row.get("pct_change")), 2),
                "price": round(_number(row.get("current_price")), 2),
                "hot": round(_number(row.get("hot")), 1),
                "rank_change": prior - rank if prior else None,
                "concepts": _concepts(row.get("concept")),
                "reason": str(row.get("rank_reason") or ""),
                "rank_time": str(row.get("rank_time") or ""),
            }
        )
    return sorted(items, key=lambda item: int(item["rank"]))


def _concepts(value: Any) -> list[str]:
    if isinstance(value, list):
        return [str(item).strip() for item in value if str(item).strip()]
    text = str(value or "").strip()
    if not text:
        return []
    try:
        parsed = json.loads(text)
        if isinstance(parsed, list):
            return [str(item).strip() for item in parsed if str(item).strip()]
    except json.JSONDecodeError:
        pass
    return [part.strip() for part in text.replace("，", ",").split(",") if part.strip()]


def _number(value: Any) -> float:
    try:
        number = float(value)
        return number if number == number else 0.0
    except (TypeError, ValueError):
        return 0.0
