from __future__ import annotations

import re
from datetime import datetime, time as dt_time
from typing import Any

from backend.bootstrap.config import normalize_date, validate_stock_code
from backend.data.providers.ifind_client import IfindError


class PoolServiceMixin:
    def save_reason(self, trade_date: str, code: str, reason: str) -> None:
        normalized_date = normalize_date(trade_date)
        code = validate_stock_code(code)
        reason = reason.strip()
        if not reason or len(reason) > 200:
            raise ValueError("涨停原因应为 1 至 200 个字符。")
        self.database.save_reason_override(normalized_date, code, reason)

    def _apply_reason_overrides(self, dashboard: dict[str, Any]) -> dict[str, Any]:
        trade_date = str(dashboard.get("meta", {}).get("trade_date", "")).replace("-", "")
        enrichment = self.database.get_data_snapshot("ifind_event_enrichment_v1", trade_date)
        if enrichment:
            self._merge_ifind_event_enrichment(dashboard, enrichment)
        else:
            self._schedule_ifind_event_enrichment(trade_date)
        overrides = self.database.reason_overrides(trade_date)
        if not overrides:
            return dashboard
        for key in ("limits", "broken", "down_limits"):
            for row in dashboard.get(key) or []:
                if row.get("code") in overrides:
                    row["reason"] = overrides[row["code"]]
                    row["reason_source"] = "manual"
        return dashboard

    def _schedule_ifind_event_enrichment(self, trade_date: str) -> None:
        ifind = getattr(self, "ifind", None)
        if not ifind or not ifind.configured or not re.fullmatch(r"\d{8}", trade_date):
            return
        now = datetime.now().astimezone()
        if trade_date == now.strftime("%Y%m%d") and now.time().replace(tzinfo=None) < dt_time(15, 0):
            return
        self.jobs.submit(
            "market.ifind-event-enrichment",
            f"{trade_date}:v1",
            lambda: self._refresh_ifind_event_enrichment(trade_date),
            {"trade_date": trade_date, "trigger": "dashboard-enrichment"},
        )

    def _refresh_ifind_event_enrichment(self, trade_date: str) -> None:
        if not self._ifind_event_lock.acquire(blocking=False):
            return
        try:
            if self.database.get_data_snapshot("ifind_event_enrichment_v1", trade_date):
                return
            ifind = getattr(self, "ifind", None)
            if not ifind or not ifind.configured:
                return
            current = datetime.strptime(trade_date, "%Y%m%d")
            display_date = f"{current.year}年{current.month}月{current.day}日"
            requests = {
                "limits": (
                    f"{display_date}涨停股票，股票代码、股票简称、涨停原因、"
                    "首次涨停时间、最终涨停时间、开板次数"
                ),
                "broken": (
                    f"{display_date}曾涨停但收盘未涨停的股票，股票代码、股票简称、"
                    "涨停原因、首次涨停时间、开板次数"
                ),
                "down_limits": (
                    f"{display_date}跌停股票，股票代码、股票简称、跌停原因"
                ),
            }
            result: dict[str, Any] = {
                "trade_date": trade_date,
                "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
                "limits": {}, "broken": {}, "down_limits": {}, "partial": False,
            }
            for kind, query in requests.items():
                try:
                    rows = ifind.wencai(query, "stock", cache_ttl=900)
                except IfindError:
                    result["partial"] = True
                    continue
                for raw in rows:
                    code = self._ifind_row_code(raw)
                    if not code:
                        continue
                    reason_tokens = (
                        ("跌停原因", "风险线索", "原因")
                        if kind == "down_limits"
                        else ("涨停原因类别", "涨停原因", "触板逻辑", "原因")
                    )
                    reason = str(self._ifind_field(raw, reason_tokens) or "").strip()
                    first_time = self._normalize_ifind_event_time(
                        self._ifind_field(raw, ("首次涨停时间", "首次触板时间", "首次封板时间"))
                    )
                    last_time = self._normalize_ifind_event_time(
                        self._ifind_field(raw, ("最终涨停时间", "最后涨停时间", "最后封板时间"))
                    )
                    open_times = self._ifind_field(raw, ("开板次数", "打开涨停次数"))
                    try:
                        open_count = max(0, int(float(open_times))) if open_times not in (None, "") else None
                    except (TypeError, ValueError):
                        open_count = None
                    result[kind][code] = {
                        "reason": reason,
                        "first_time": first_time,
                        "last_time": last_time,
                        "open_times": open_count,
                    }
            if any(result[kind] for kind in ("limits", "broken", "down_limits")):
                self.database.save_data_snapshot(
                    "ifind_event_enrichment_v1", trade_date, "ifind", result
                )
        finally:
            self._ifind_event_lock.release()

    @staticmethod
    def _ifind_field(row: dict[str, Any], tokens: tuple[str, ...]) -> Any:
        for key, value in row.items():
            label = str(key or "")
            if any(token.casefold() == label.casefold() for token in tokens):
                return value
        for key, value in row.items():
            label = str(key or "")
            if any(token in label for token in tokens):
                return value
        return None

    @classmethod
    def _ifind_row_code(cls, row: dict[str, Any]) -> str:
        value = cls._ifind_field(row, ("股票代码", "证券代码", "代码", "thscode"))
        match = re.search(r"(?<!\d)(\d{6})(?!\d)", str(value or ""))
        if match:
            return match.group(1)
        for value in row.values():
            match = re.search(r"(?<!\d)(\d{6})\.(?:SH|SZ|BJ)(?![A-Z])", str(value or ""), re.I)
            if match:
                return match.group(1)
        return ""

    @staticmethod
    def _normalize_ifind_event_time(value: Any) -> str:
        text = str(value or "").strip()
        match = re.search(r"(?:^|\s)(\d{1,2}:\d{2}(?::\d{2})?)(?:$|\s)", text)
        if not match:
            match = re.search(r"(?<!\d)(\d{6})(?!\d)", text)
            if match:
                compact = match.group(1)
                return f"{compact[:2]}:{compact[2:4]}:{compact[4:]}"
            return ""
        parts = match.group(1).split(":")
        return ":".join(part.zfill(2) for part in parts)

    @staticmethod
    def _merge_ifind_event_enrichment(
        dashboard: dict[str, Any], enrichment: dict[str, Any]
    ) -> None:
        for kind in ("limits", "broken", "down_limits"):
            records = enrichment.get(kind) or {}
            for row in dashboard.get(kind) or []:
                event = records.get(str(row.get("code") or "")) or {}
                reason = str(event.get("reason") or "").strip()
                if reason:
                    row["reason"] = reason
                    row["reason_source"] = "market_event"
                if event.get("first_time"):
                    row["first_time"] = event["first_time"]
                if event.get("last_time"):
                    row["last_time"] = event["last_time"]
                if event.get("open_times") is not None:
                    row["open_times"] = event["open_times"]
