from __future__ import annotations

import copy
from datetime import datetime, timedelta
from typing import Any

from backend.data.numbers import non_nan_number as _number
from backend.data.providers.tushare_client import TushareError
from backend.features.market.insights_context import _display_date


class MarketPopularityInsightsMixin:
    def popularity(self, requested_date: str, force: bool = False) -> dict[str, Any]:
        trade_date, previous_date = self._trade_context(requested_date)
        if not force:
            cached = self.database.get_data_snapshot("popularity_v1", trade_date)
            if cached:
                result = copy.deepcopy(cached)
                result["meta"] = {**result.get("meta", {}), "cached": True}
                return result

        ths_rows, dc_rows, errors = self._hot_rows(trade_date)
        actual_date = trade_date
        carried_forward = False
        if not ths_rows and not dc_rows and previous_date:
            ths_rows, dc_rows, errors = self._hot_rows(previous_date)
            actual_date = previous_date
            carried_forward = bool(ths_rows or dc_rows)
        if not ths_rows and not dc_rows:
            fallback = self._latest_feature_snapshot("popularity_v1", trade_date)
            if fallback:
                result = copy.deepcopy(fallback)
                result["meta"] = {
                    **result.get("meta", {}),
                    "requested_date": _display_date(requested_date),
                    "carried_forward": True,
                    "cached": True,
                    "notice": "当前榜单暂不可用，展示最近有效快照",
                }
                return result
            return {
                "meta": {
                    "requested_date": _display_date(requested_date),
                    "trade_date": _display_date(trade_date),
                    "previous_trade_date": _display_date(previous_date),
                    "carried_forward": False,
                    "cached": False,
                    "notice": "该交易日暂无可用人气榜",
                    "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
                },
                "summary": {"ths_count": 0, "dc_count": 0, "dual_count": 0},
                "combined": [], "ths": [], "dc": [],
            }

        prior_request = (datetime.strptime(actual_date, "%Y%m%d") - timedelta(days=1)).strftime("%Y%m%d")
        prior_date, _ = self._trade_context(prior_request)
        previous_ths, previous_dc, _ = self._hot_rows(prior_date)
        ths = self._normalize_hot(ths_rows, "热股", previous_ths)
        dc = self._normalize_hot(dc_rows, "A股市场", previous_dc)
        ths_map = {item["ts_code"]: item for item in ths}
        dc_map = {item["ts_code"]: item for item in dc}
        combined = []
        for ts_code in set(ths_map) | set(dc_map):
            ths_item = ths_map.get(ts_code)
            dc_item = dc_map.get(ts_code)
            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": (ths_item or {}).get("concepts") or [],
                }
            )
        combined.sort(key=lambda item: (item["dual_source"], item["score"]), reverse=True)
        for index, item in enumerate(combined, 1):
            item["rank"] = index
        result = {
            "meta": {
                "requested_date": _display_date(requested_date),
                "trade_date": _display_date(actual_date),
                "previous_trade_date": _display_date(prior_date),
                "carried_forward": carried_forward,
                "cached": False,
                "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
                "notice": "；".join(errors),
            },
            "summary": {
                "ths_count": len(ths),
                "dc_count": len(dc),
                "dual_count": sum(item["dual_source"] for item in combined),
            },
            "combined": combined[:200],
            "ths": ths,
            "dc": dc,
        }
        self.database.save_data_snapshot("popularity_v1", trade_date, "market", result)
        return result

    def _hot_rows(self, trade_date: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[str]]:
        errors = []
        try:
            ths = self.client.query("ths_hot", {"trade_date": trade_date})
        except TushareError:
            ths = []
            errors.append("同花顺榜单暂不可用")
        try:
            dc = self.client.query("dc_hot", {"trade_date": trade_date})
        except TushareError:
            dc = []
            errors.append("东方财富榜单暂不可用")
        return ths, dc, errors

    def _normalize_hot(
        self,
        rows: list[dict[str, Any]],
        data_type: str,
        previous_rows: list[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
            rank = int(_number(row.get("rank")))
            ts_code = str(row.get("ts_code") or "")
            prior_rank = previous.get(ts_code)
            items.append(
                {
                    "rank": rank,
                    "ts_code": ts_code,
                    "code": ts_code.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 - rank) if prior_rank else None,
                    "concepts": self._parse_concepts(row.get("concept")),
                    "reason": str(row.get("rank_reason") or ""),
                    "rank_time": str(row.get("rank_time") or ""),
                }
            )
        items.sort(key=lambda item: item["rank"])
        return items
