from __future__ import annotations

import copy
from datetime import datetime, time as dt_time, timedelta
from typing import Any

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


class MarketAuctionDataMixin:
    def _auction_session(self, requested_date: str, trade_date: str) -> dict[str, Any]:
        now = self._now_provider()
        if now.tzinfo is None:
            now = now.replace(tzinfo=CHINA_TIMEZONE)
        else:
            now = now.astimezone(CHINA_TIMEZONE)
        requested = str(requested_date or "").replace("-", "")
        today = now.strftime("%Y%m%d")
        if requested != today or trade_date != today:
            return {
                "phase": "archive",
                "actionable": False,
                "next_transition_at": "",
            }

        local_time = now.time().replace(tzinfo=None)
        transitions = (
            (dt_time(9, 15), "pending", dt_time(9, 15)),
            (dt_time(9, 25), "observing", dt_time(9, 25)),
            (dt_time(9, 30), "selection", dt_time(9, 30)),
        )
        for boundary, phase, next_boundary in transitions:
            if local_time < boundary:
                transition = now.replace(
                    hour=next_boundary.hour,
                    minute=next_boundary.minute,
                    second=0,
                    microsecond=0,
                )
                return {
                    "phase": phase,
                    "actionable": phase == "selection",
                    "next_transition_at": transition.isoformat(timespec="seconds"),
                }
        return {
            "phase": "finalized",
            "actionable": False,
            "next_transition_at": "",
        }

    def _auction_amount_history(self, trade_date: str) -> list[dict[str, Any]]:
        dates = self.database.auction_factor_dates(trade_date, 10)
        stock_list_dates = {
            str(item.get("ts_code") or ""): str(item.get("list_date") or "")
            for item in self.database.list_stock_master()
            if item.get("ts_code")
        }
        history = []
        for current_date in dates:
            rows = [
                row for row in self.database.auction_factors_for_date(current_date)
                if (
                    str(row.get("ts_code") or "") in stock_list_dates
                    and (
                        not stock_list_dates[str(row.get("ts_code") or "")]
                        or stock_list_dates[str(row.get("ts_code") or "")] < current_date
                    )
                )
            ]
            history.append(
                {
                    "trade_date": _display_date(current_date),
                    "amount_billion": round(sum(_number(row.get("amount")) for row in rows) / 100_000_000, 2),
                    "stock_count": len(rows),
                }
            )
        return history

    def _ensure_auction_amount_history(self, trade_date: str, target_days: int = 10) -> None:
        existing = set(self.database.auction_factor_dates(trade_date, target_days + 5))
        if len(existing) >= target_days:
            return
        end = datetime.strptime(trade_date, "%Y%m%d")
        start = (end - timedelta(days=35)).strftime("%Y%m%d")
        try:
            calendar = self.client.query(
                "trade_cal",
                {
                    "exchange": "SSE",
                    "start_date": start,
                    "end_date": trade_date,
                    "is_open": 1,
                },
                "cal_date,is_open",
            )
        except TushareError:
            return
        dates = sorted(
            str(item.get("cal_date") or "")
            for item in calendar
            if int(_number(item.get("is_open"))) == 1 and item.get("cal_date")
        )[-target_days:]
        for current_date in dates:
            if current_date in existing:
                continue
            try:
                rows = self.client.query(
                    "stk_auction",
                    {"trade_date": current_date},
                    "ts_code,trade_date,vol,price,amount,pre_close,turnover_rate,volume_ratio,float_share",
                )
            except TushareError:
                break
            if rows:
                self.database.upsert_auction_factors(rows)
                existing.add(current_date)

    def _with_auction_watchlist(
        self,
        result: dict[str, Any],
        trade_date: str,
        user_id: int,
    ) -> dict[str, Any]:
        personalized = copy.deepcopy(result)
        if not user_id:
            personalized["watchlist_rows"] = []
            personalized["watchlist_missing_count"] = 0
            return personalized
        watched = self.database.list_watchlist(user_id)
        if not watched:
            personalized["watchlist_rows"] = []
            personalized["watchlist_missing_count"] = 0
            return personalized

        public_rows = {
            str(item.get("code") or ""): item
            for item in (
                list(personalized.get("rows") or [])
                + list(personalized.get("one_price_rows") or [])
            )
        }
        factors = {
            str(item.get("ts_code") or "").split(".")[0]: item
            for item in self.database.auction_factors_for_date(trade_date)
        }
        master = {
            str(item.get("ts_code") or "").split(".")[0]: item
            for item in self.database.list_stock_master()
        }
        rows = []
        missing = 0
        for item in watched:
            code = str(item.get("code") or "")
            if code in public_rows:
                rows.append({**public_rows[code], "is_watchlist": True})
                continue
            factor = factors.get(code)
            if not factor:
                missing += 1
                rows.append(
                    {
                        "code": code,
                        "name": str(item.get("name") or "--"),
                        "sector": str(item.get("sector") or "其他"),
                        "available": False,
                        "is_watchlist": True,
                    }
                )
                continue
            stock = master.get(code, {})
            price = _number(factor.get("price"))
            pre_close = _number(factor.get("pre_close"))
            change = (price / pre_close - 1) * 100 if price > 0 and pre_close > 0 else 0
            row = {
                "code": code,
                "ts_code": str(factor.get("ts_code") or ""),
                "name": str(item.get("name") or stock.get("name") or "--"),
                "sector": str(item.get("sector") or stock.get("industry") or "其他"),
                "price": round(price, 2),
                "pre_close": round(pre_close, 2),
                "change": round(change, 2),
                "amount_million": round(_number(factor.get("amount")) / 1_000_000, 2),
                "turnover_rate": round(_number(factor.get("turnover_rate")), 4),
                "volume_ratio": round(_number(factor.get("volume_ratio")), 2),
                "candidate_sources": ["我的自选"],
                "source_label": "我的自选",
                "prior_streak": 0,
                "concepts": [],
                "expected_change": 0.0,
                "core_tags": [],
                "is_market_core": False,
                "is_watchlist": True,
                "available": True,
            }
            actual_strength = change + self._auction_confirmation(row)
            row["actual_strength"] = round(actual_strength, 2)
            row["expectation"] = self._expectation_label(actual_strength, 0.0)
            row["attention_score"] = self._attention_score(row, 0.0, [], ["我的自选"], 0, False)
            direction = "高于" if change > 0 else "低于" if change < 0 else "贴合"
            row["expectation_reason"] = f"自选观察；竞价涨幅{direction}个人观察基准{abs(change):.1f}个百分点，量比{row['volume_ratio']:.2f}"
            rows.append(row)
        rows.sort(
            key=lambda row: (bool(row.get("available", True)), _number(row.get("attention_score"))),
            reverse=True,
        )
        personalized["watchlist_rows"] = rows
        personalized["watchlist_missing_count"] = missing
        return personalized

    def _dynamic_auction_rows(
        self,
        trade_date: str,
        baseline_date: str,
        user_id: int,
    ) -> list[dict[str, Any]]:
        if not self.ifind or not self.ifind.configured:
            return []
        master = self._stock_master()
        placeholders = [
            {
                "code": str(item.get("code") or ts_code.split(".")[0]),
                "ts_code": ts_code,
                "name": str(item.get("name") or "--"),
                "sector": str(item.get("industry") or "其他"),
            }
            for ts_code, item in master.items()
        ]
        candidates, _, _ = self._auction_candidates(placeholders, baseline_date)
        selected_codes = {
            str(item.get("ts_code") or "")
            for item in candidates
            if item.get("ts_code")
        }
        if user_id:
            watched = {str(item.get("code") or "") for item in self.database.list_watchlist(user_id)}
            selected_codes.update(
                ts_code for ts_code in master if ts_code.split(".")[0] in watched
            )
        selected_codes.discard("")
        if not selected_codes:
            return []

        display_date = _display_date(trade_date)
        now = self._now_provider()
        if now.tzinfo is None:
            now = now.replace(tzinfo=CHINA_TIMEZONE)
        else:
            now = now.astimezone(CHINA_TIMEZONE)
        end_time = min(now.time().replace(tzinfo=None), dt_time(9, 25))
        end_stamp = f"{display_date} {end_time.strftime('%H:%M:%S')}"
        start_stamp = f"{display_date} 09:15:00"
        snapshot_rows: list[dict[str, Any]] = []
        ordered_codes = sorted(selected_codes)
        for index in range(0, len(ordered_codes), 80):
            try:
                snapshot_rows.extend(
                    self.ifind.snapshots(
                        ordered_codes[index:index + 80],
                        [
                            "latest", "volume", "amount", "preClose",
                            "bid1", "bidSize1", "ask1", "askSize1",
                        ],
                        start_stamp,
                        end_stamp,
                        cache_ttl=8,
                    )
                )
            except IfindError:
                continue

        latest: dict[str, dict[str, Any]] = {}
        for row in snapshot_rows:
            ts_code = str(row.get("thscode") or "")
            previous = latest.get(ts_code) or {}
            if (
                ts_code
                and _number(row.get("latest")) > 0
                and str(row.get("time") or "") >= str(previous.get("time") or "")
            ):
                latest[ts_code] = row
        prior_factors = {
            str(item.get("ts_code") or ""): item
            for item in self.database.auction_factors_for_date(baseline_date)
        }
        normalized = []
        for ts_code, row in latest.items():
            price = _number(row.get("latest"))
            pre_close = _number(row.get("preClose"))
            volume = _number(row.get("volume"))
            bid_size = _number(row.get("bidSize1"))
            ask_size = _number(row.get("askSize1"))
            if volume <= 0 and bid_size > 0 and ask_size > 0:
                volume = min(bid_size, ask_size)
            amount = _number(row.get("amount"))
            if amount <= 0 and price > 0 and volume > 0:
                amount = price * volume
            prior_volume = _number((prior_factors.get(ts_code) or {}).get("vol"))
            normalized.append(
                {
                    "ts_code": ts_code,
                    "trade_date": trade_date,
                    "vol": volume,
                    "price": price,
                    "amount": amount,
                    "pre_close": pre_close,
                    "turnover_rate": 0,
                    "volume_ratio": volume / prior_volume if prior_volume > 0 else 0,
                    "float_share": 0,
                    "bid_size1": bid_size,
                    "ask_size1": ask_size,
                    "snapshot_time": str(row.get("time") or ""),
                    "dynamic": True,
                }
            )
        return normalized
