from __future__ import annotations

from datetime import datetime
from typing import Any

from backend.bootstrap.config import normalize_date
from backend.data.providers.tushare_client import _sector_coverage_issue
from backend.features.heaven.agent import HEAVEN_PROMPT_VERSIONS
from backend.features.heaven.engine import build_five_phase_field, build_market_hexagram


class HeavenTrendMixin:
    def heaven_setup(
        self,
        trade_date: str,
        sector_name: str = "",
        stock_code: str = "",
        manual_data: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        normalized_date = normalize_date(trade_date)
        dashboard = self.get_dashboard(normalized_date)
        data_date = normalize_date(str(dashboard.get("meta", {}).get("trade_date") or normalized_date))
        recent_history = self.database.snapshot_summaries(data_date, 10)
        market_mode = self._heaven_market_mode(data_date, dashboard)
        manual_data = self._validate_heaven_manual_data(manual_data, market_mode)
        index_context = self._heaven_index_context(data_date, dashboard, market_mode)
        external_stock = None
        normalized_stock_code = ""
        if stock_code.strip():
            normalized_stock_code = self._resolve_heaven_stock_code(stock_code)
            external_stock = self._heaven_stock_context(
                normalized_stock_code,
                data_date,
                dashboard,
                market_mode,
            )
        external_sector = None
        if normalized_stock_code and self.configured:
            external_sector = self._heaven_sector_context(
                normalized_stock_code,
                data_date,
                market_mode,
            )
            if external_sector and external_stock:
                external_stock["sector"] = external_sector.get("name") or external_stock.get("sector")
        dashboard, index_context, external_sector, external_stock = self._apply_heaven_manual_data(
            dashboard,
            index_context,
            external_sector,
            external_stock,
            manual_data,
            market_mode,
            data_date,
            normalized_stock_code,
        )
        if external_sector and external_stock:
            external_stock["sector"] = external_sector.get("name") or external_stock.get("sector")
        sector_input = str((external_sector or {}).get("name") or sector_name.strip())
        if not normalized_stock_code:
            data_checks = []
            chart = {
                "available": False,
                "selection_required": True,
                "data_trade_date": data_date,
                "sector": "",
                "sector_code": "",
                "sector_taxonomy": "",
                "stock": {"code": "", "name": "", "status": ""},
                "quality": {
                    "status": "awaiting_selection",
                    "issues": [],
                    "principle": "",
                    "sources": [],
                },
                "index_context": index_context,
            }
        else:
            data_checks = self._heaven_line_checks(
                data_date,
                dashboard,
                recent_history,
                index_context,
                external_sector or {},
                external_stock or {},
                market_mode,
                manual_data,
            )
            quality_issues = [
                f"{check['position']}·{check['layer']}：{'；'.join(check['reasons'])}"
                for check in data_checks
                if not check["passed"]
            ]
            if quality_issues:
                chart = {
                    "available": False,
                    "selection_required": False,
                    "data_trade_date": data_date,
                    "sector": str((external_sector or {}).get("name") or sector_input or "--"),
                    "sector_code": str((external_sector or {}).get("code") or ""),
                    "sector_taxonomy": str((external_sector or {}).get("taxonomy") or ""),
                    "stock": {
                        "code": normalized_stock_code,
                        "name": str((external_stock or {}).get("name") or "--"),
                        "status": str((external_stock or {}).get("status") or ""),
                    },
                    "quality": {
                        "status": "blocked",
                        "issues": quality_issues,
                        "principle": "六爻任一层缺少同日、同口径的有效数据，本系统不成卦。",
                        "sources": self._heaven_trend_sources(
                            data_date, index_context, external_sector, external_stock
                        ),
                    },
                    "index_context": index_context,
                }
            else:
                chart = build_market_hexagram(
                    dashboard,
                    recent_history,
                    index_context,
                    sector_input,
                    normalized_stock_code,
                    external_stock,
                    external_sector,
                )
                chart["available"] = True
                chart["selection_required"] = False
                manual_active = any(check["status"] == "manual" for check in data_checks)
                chart["quality"] = {
                    "status": "manual" if manual_active else "verified",
                    "issues": [],
                    "principle": (
                        "自动行情与用户补充数据均已通过同一套量化公式校验。"
                        if manual_active
                        else "指数、板块、个股均已通过同日同口径校验。"
                    ),
                    "sources": [
                        *self._heaven_trend_sources(
                            data_date, index_context, external_sector, external_stock
                        ),
                        *([{
                            "lines": "补录爻位",
                            "layer": "用户补充",
                            "realtime": market_mode == "intraday",
                            "detail": str(manual_data.get("note") or "量化数据经原公式重新计算"),
                        }] if manual_active else []),
                    ],
                }
        chart["data_checks"] = data_checks
        chart["manual_data"] = manual_data
        sector_phase_overrides = self.database.list_sector_phase_overrides()
        field = build_five_phase_field(
            normalized_date,
            sector_phase_overrides,
        )
        personal_profile = self.account_personal_field(
            normalized_date,
            field,
            public=True,
        )
        daily_fortune_reading = self._reusable_daily_fortune_reading(normalized_date)
        return {
            "trade_date": data_date,
            "calendar_date": normalized_date,
            "market_mode": market_mode,
            "chart": chart,
            "field": field,
            "personal_profile": personal_profile,
            "daily_fortune_reading": daily_fortune_reading,
            "sector_phase_overrides": [
                {"name": name, "element": element}
                for name, element in sector_phase_overrides.items()
            ],
            "llm": {
                "configured": self.llm_configured,
                "model": self.llm_primary_model if self.llm_configured else "",
                "fallback_configured": self.llm_fallback_configured,
                "fallback_model": self.llm_fallback_model if self.llm_fallback_configured else "",
            },
        }

    def _reusable_daily_fortune_reading(
        self, context_date: str
    ) -> dict[str, Any] | None:
        reading = self.database.latest_heaven_reading(
            self.current_user_id, "fortune", context_date
        )
        if not reading or self._legacy_truncated_heaven_reading(reading):
            return None
        version = self.database.heaven_reading_interpretation_version(
            self.current_user_id, int(reading["id"])
        )
        if version != HEAVEN_PROMPT_VERSIONS["fortune"]:
            return None
        return reading

    @staticmethod
    def _heaven_market_mode(
        trade_date: str,
        dashboard: dict[str, Any],
        now: datetime | None = None,
    ) -> str:
        """区分盘中、今日收盘和历史，避免把 rt_k 数据来源误当成交易状态。"""
        now = now or datetime.now().astimezone()
        if trade_date != now.strftime("%Y%m%d"):
            return "historical"
        meta = dashboard.get("meta") or {}
        status = str(meta.get("market_status") or "").lower()
        local_time = now.time().replace(tzinfo=None)
        if status == "closed" or local_time > datetime.strptime("15:05", "%H:%M").time():
            return "closed"
        if status in {"trading", "auction", "pre_open"} or (
            bool(meta.get("realtime"))
            and local_time >= datetime.strptime("09:15", "%H:%M").time()
        ):
            return "intraday"
        return "historical"

    @staticmethod
    def _heaven_trend_sources(
        trade_date: str,
        index_context: dict[str, Any],
        sector: dict[str, Any] | None,
        stock: dict[str, Any] | None,
    ) -> list[dict[str, Any]]:
        sector = sector or {}
        stock = stock or {}
        return [
            {
                "lines": "五爻、上爻",
                "layer": "指数",
                "source": index_context.get("source") or "unavailable",
                "trade_date": index_context.get("trade_date") or "",
                "realtime": bool(index_context.get("realtime")),
                "detail": f"三大指数 {len(index_context.get('indices') or [])}/3",
            },
            {
                "lines": "三爻、四爻",
                "layer": "行业",
                "source": sector.get("source") or "unavailable",
                "trade_date": sector.get("trade_date") or "",
                "realtime": bool(sector.get("realtime")),
                "detail": (
                    f"申万二级 {sector.get('name') or '--'} {sector.get('code') or '--'} "
                    f"成分覆盖 {int(sector.get('quote_count') or 0)}/{int(sector.get('member_count') or 0)}"
                ),
            },
            {
                "lines": "初爻、二爻",
                "layer": "个股",
                "source": stock.get("data_source") or "unavailable",
                "trade_date": stock.get("trade_date") or trade_date,
                "realtime": bool(stock.get("realtime")),
                "detail": (
                    f"{stock.get('name') or '--'}；换手基准 "
                    f"{stock.get('capital_trade_date') or '--'}"
                ),
            },
        ]

    @staticmethod
    def _heaven_trend_quality_issues(
        trade_date: str,
        dashboard: dict[str, Any],
        index_context: dict[str, Any],
        sector: dict[str, Any] | None,
        stock: dict[str, Any] | None,
        market_mode: str = "historical",
    ) -> list[str]:
        issues: list[str] = []
        intraday = market_mode == "intraday"
        closed = market_mode == "closed"
        if intraday:
            meta = dashboard.get("meta") or {}
            market_status = str(meta.get("market_status") or "")
            now = datetime.now().astimezone()
            try:
                updated_at = datetime.fromisoformat(str(meta.get("updated_at") or ""))
                if updated_at.tzinfo is None:
                    updated_at = updated_at.replace(tzinfo=now.tzinfo)
                snapshot_age = (now - updated_at.astimezone(now.tzinfo)).total_seconds()
            except ValueError:
                snapshot_age = float("inf")
            if market_status in {"trading", "auction", "pre_open"} and snapshot_age > 120:
                issues.append("主行情快照超过2分钟，请点击顶部刷新")
        # 收盘后不再用 dashboard.market_status 作为阻断条件。盘后同步可能将
        # rt_k 快照替换成同日盘后日线而不带该字段；六爻数据本身的日期、
        # 完整性和来源校验已足以判断是否可以成卦。

        index_date = str(index_context.get("trade_date") or "").replace("-", "")
        index_rows = list(index_context.get("indices") or [])
        index_row_dates = {
            str(row.get("trade_date") or "").replace("-", "") for row in index_rows
        }
        if not index_context.get("precise") or len(index_rows) < 3:
            issues.append("指数层缺少三大指数的有效行情")
        elif index_date != trade_date or index_row_dates != {trade_date}:
            issues.append("指数行情与目标交易日不一致")
        elif intraday and not index_context.get("realtime"):
            issues.append("盘中指数层缺少可核验的实时行情")
        elif not intraday and (
            index_context.get("realtime")
            or str(index_context.get("source") or "") != "tushare"
        ):
            issues.append("历史/收盘指数层必须使用 Tushare 官方指数日线")

        sector = sector or {}
        sector_date = str(sector.get("trade_date") or "").replace("-", "")
        sector_coverage = float(sector.get("coverage") or 0)
        sector_explained_count = int(
            sector.get("explained_count")
            if sector.get("explained_count") is not None
            else sector.get("quote_count") or 0
        )
        sector_explained_coverage = float(
            sector.get("explained_coverage")
            if sector.get("explained_coverage") is not None
            else sector_coverage
        )
        sector_coverage_issue = _sector_coverage_issue(
            int(sector.get("member_count") or 0),
            int(sector.get("quote_count") or 0),
            sector_explained_coverage,
            sector_explained_count,
        )
        if not sector:
            issues.append("行业层缺少申万二级行业归属")
        elif sector.get("taxonomy") != "sw_l2":
            issues.append("行业层必须使用申万二级行业分类")
        elif sector_date != trade_date:
            issues.append("行业行情与目标交易日不一致")
        elif intraday and not sector.get("realtime"):
            issues.append("盘中行业层缺少申万实时行情")
        elif market_mode == "historical" and sector.get("realtime"):
            issues.append("历史行业层不能使用实时快照")
        elif closed and sector.get("realtime") and not sector.get("finalized"):
            issues.append("收盘行业层缺少15:00最终快照")
        if not sector.get("inner_precise", sector.get("precise")):
            issues.append("行业内核缺少可核验的成分行情")
        if not sector.get("outer_precise", sector.get("precise")):
            issues.append("行业外显缺少申万官方行情")
        if sector and sector_coverage_issue:
            issues.append(sector_coverage_issue)
        if sector.get("realtime") and not sector.get("relative_turnover"):
            issues.append("行业内核缺少相对全市场换手活跃度")

        stock = stock or {}
        stock_date = str(stock.get("trade_date") or "").replace("-", "")
        if not stock or not stock.get("code"):
            issues.append("个股层尚未载入有效标的")
        elif not stock.get("precise"):
            issues.append("个股层缺少可核验的行情数据")
        elif stock_date != trade_date:
            issues.append("个股行情与目标交易日不一致")
        elif intraday and not stock.get("realtime"):
            issues.append("盘中个股层不是 rt_k 实时行情")
        elif not intraday and (
            stock.get("realtime")
            or str(stock.get("data_source") or "") != "tushare"
        ):
            issues.append("历史/收盘个股层必须使用 Tushare 官方日线")
        if intraday and stock and not stock.get("turnover_source"):
            issues.append("个股内核缺少可核验的实时换手率")
        elif intraday and stock.get("turnover_source") == "unavailable":
            issues.append("个股内核缺少流通股本，无法计算实时换手率")
        if intraday and stock.get("activity_source") == "unavailable":
            issues.append("个股内核缺少近5日量能基准")
        elif intraday and not stock.get("activity_source"):
            issues.append("个股内核缺少同时间进度量能")
        return issues
