from __future__ import annotations

import copy
from datetime import datetime
from statistics import median
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 MarketAuctionInsightsMixin:
    def auction_center(
        self,
        requested_date: str,
        force: bool = False,
        user_id: int = 0,
    ) -> dict[str, Any]:
        trade_date, previous_date = self._trade_context(requested_date)
        session = self._auction_session(requested_date, trade_date)
        phase = str(session["phase"])
        ifind_ready = bool(self.ifind and self.ifind.configured)
        live_dynamic = phase == "observing" and ifind_ready
        use_ifind_snapshot = phase in {"observing", "selection", "finalized"} and ifind_ready
        data_date = previous_date if phase == "pending" or (phase == "observing" and not live_dynamic) else trade_date
        carried_forward = data_date != trade_date
        cache_key = data_date
        if not force and not live_dynamic:
            cached = self.database.get_data_snapshot("auction_center_v6", cache_key)
            if cached:
                result = copy.deepcopy(cached)
                result["meta"] = {
                    **result.get("meta", {}),
                    **session,
                    "requested_date": _display_date(requested_date),
                    "trade_date": _display_date(data_date),
                    "carried_forward": carried_forward,
                    "available": bool((result.get("summary") or {}).get("stock_count")),
                    "cached": True,
                }
                return self._with_auction_watchlist(result, data_date, user_id)

        if use_ifind_snapshot:
            rows = self._dynamic_auction_rows(data_date, previous_date, user_id)
        else:
            rows = []
        if not rows and not live_dynamic:
            try:
                rows = self.client.query("stk_auction", {"trade_date": data_date})
            except TushareError:
                rows = self.database.auction_factors_for_date(data_date)
        if not rows:
            return {
                "meta": {
                    **session,
                    "requested_date": _display_date(requested_date),
                    "trade_date": _display_date(data_date),
                    "carried_forward": carried_forward,
                    "available": False,
                    "cached": False,
                    "notice": "该交易日暂无可用竞价快照",
                    "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
                },
                "summary": {
                    "stock_count": 0, "up_count": 0, "down_count": 0,
                    "limit_open_count": 0, "strong_open_count": 0,
                    "median_change": 0, "amount_billion": 0,
                    "candidate_count": 0, "focus_count": 0, "one_price_count": 0,
                },
                "expectations": {"超预期": 0, "符合预期": 0, "低于预期": 0},
                "candidate_meta": {"baseline_date": _display_date(previous_date)},
                "themes": {"carry": [], "new_themes": []},
                "amount_history": self._auction_amount_history(data_date),
                "news_feedback": {"available": False, "message": "隔夜消息反馈暂不可用"},
                "focus_rows": [], "one_price_rows": [], "rows": [],
                "watchlist_rows": [], "watchlist_missing_count": 0,
            }

        master = self._stock_master()
        try:
            limit_rows = self.client.query(
                "stk_limit",
                {"trade_date": data_date},
                "trade_date,ts_code,up_limit,down_limit",
            )
        except TushareError:
            limit_rows = []
        limit_map = {str(item.get("ts_code") or ""): item for item in limit_rows}
        normalized = []
        for row in rows:
            ts_code = str(row.get("ts_code") or "")
            stock = master.get(ts_code)
            price = _number(row.get("price"))
            pre_close = _number(row.get("pre_close"))
            list_date = str((stock or {}).get("list_date") or "")
            if (
                not stock
                or price <= 0
                or pre_close <= 0
                or (list_date and list_date >= data_date)
            ):
                continue
            change = (price / pre_close - 1) * 100
            amount_million = _number(row.get("amount")) / 1_000_000
            volume_ratio = _number(row.get("volume_ratio"))
            turnover_rate = _number(row.get("turnover_rate"))
            up_limit = _number((limit_map.get(ts_code) or {}).get("up_limit"))
            is_one_price = bool(
                up_limit > 0 and abs(price - up_limit) <= max(0.001, up_limit * 0.00005)
            )
            normalized.append(
                {
                    "code": str(stock.get("code") or ts_code.split(".")[0]),
                    "ts_code": ts_code,
                    "name": str(stock.get("name") or "--"),
                    "sector": str(stock.get("industry") or "其他"),
                    "price": round(price, 2),
                    "pre_close": round(pre_close, 2),
                    "change": round(change, 2),
                    "volume_ten_thousand": round(_number(row.get("vol")) / 10_000, 2),
                    "amount_million": round(amount_million, 2),
                    "turnover_rate": round(turnover_rate, 4),
                    "volume_ratio": round(volume_ratio, 2),
                    "up_limit": round(up_limit, 2) if up_limit else None,
                    "is_one_price": is_one_price,
                    "signal": (
                        "竞价涨停" if change >= 9.5 else
                        "强势高开" if change >= 3 else
                        "高开" if change > 0.2 else
                        "深度低开" if change <= -3 else
                        "低开" if change < -0.2 else "平开"
                    ),
                }
            )
        normalized.sort(key=lambda item: (item["amount_million"], item["volume_ratio"]), reverse=True)
        self.database.upsert_auction_factors(rows)
        changes = [item["change"] for item in normalized]
        total = len(normalized)
        _, baseline_date = self._trade_context(data_date)
        candidates, candidate_meta, focus_rows = self._auction_candidates(normalized, baseline_date)
        candidate_map = {str(item.get("code") or ""): item for item in candidates}
        one_price_rows = []
        for row in normalized:
            if not row.get("is_one_price"):
                continue
            enriched = candidate_map.get(str(row.get("code") or ""), {})
            one_price_rows.append(
                {
                    **row,
                    **enriched,
                    "attention_score": None,
                    "expectation": "",
                    "expected_change": None,
                    "expectation_reason": "竞价价格封于当日涨停价，已从普通异动评分中隔离",
                }
            )
        one_price_codes = {str(item.get("code") or "") for item in one_price_rows}
        candidates = [item for item in candidates if str(item.get("code") or "") not in one_price_codes]
        focus_rows = [item for item in focus_rows if str(item.get("code") or "") not in one_price_codes]
        one_price_rows.sort(
            key=lambda item: (
                bool(item.get("is_market_core")),
                _number(item.get("prior_streak")),
                _number(item.get("amount_million")),
            ),
            reverse=True,
        )
        expectations = {
            label: sum(item.get("expectation") == label for item in candidates)
            for label in ("超预期", "符合预期", "低于预期")
        }
        prior_snapshot = self.database.get_snapshot(baseline_date) or {}
        themes = self._auction_theme_evidence(prior_snapshot, candidates + one_price_rows)
        self._ensure_auction_amount_history(data_date)
        amount_history = self._auction_amount_history(data_date)
        prior_amounts = [item["amount_billion"] for item in amount_history[:-1]]
        current_amount = round(sum(item["amount_million"] for item in normalized) / 100, 2)
        previous_amount = prior_amounts[-1] if prior_amounts else 0
        five_day_amounts = prior_amounts[-5:]
        five_day_average = sum(five_day_amounts) / len(five_day_amounts) if five_day_amounts else 0
        result = {
            "meta": {
                "requested_date": _display_date(requested_date),
                "trade_date": _display_date(data_date),
                "carried_forward": carried_forward,
                "available": bool(normalized),
                **session,
                "cached": False,
                "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
            },
            "summary": {
                "stock_count": total,
                "up_count": sum(value > 0.2 for value in changes),
                "down_count": sum(value < -0.2 for value in changes),
                "limit_open_count": len(one_price_rows),
                "strong_open_count": sum(value >= 3 for value in changes),
                "median_change": round(median(changes), 2) if changes else 0,
                "amount_billion": current_amount,
                "amount_change_previous": round((current_amount / previous_amount - 1) * 100, 1) if previous_amount else None,
                "amount_change_5d": round((current_amount / five_day_average - 1) * 100, 1) if five_day_average else None,
                "candidate_count": len(candidates),
                "focus_count": len(focus_rows),
                "one_price_count": len(one_price_rows),
            },
            "expectations": expectations,
            "candidate_meta": candidate_meta,
            "themes": themes,
            "amount_history": amount_history,
            "news_feedback": {
                "available": False,
                "message": "隔夜消息反馈暂不可用",
                "detail": "待稳定的新闻与公告数据接入后开放",
            },
            "focus_rows": focus_rows,
            "one_price_rows": one_price_rows,
            "rows": candidates,
        }
        if not live_dynamic:
            self.database.save_data_snapshot("auction_center_v6", cache_key, "market", result)
        return self._with_auction_watchlist(result, data_date, user_id)
