from __future__ import annotations

import json
import re
from typing import Any

from backend.data.gateway import DataGateway
from backend.data.repository import MarketRepository
from backend.database.connection import Database
from backend.features.mentor.skills import MentorSkill
from backend.features.screener.repository import ScreenerRepository

INDEX_UNIVERSE = (
    ("index", "000001.SH", "上证指数"),
    ("index", "399001.SZ", "深证成指"),
    ("index", "399006.SZ", "创业板指"),
    ("index", "000016.SH", "上证50"),
    ("index", "000300.SH", "沪深300"),
    ("index", "000905.SH", "中证500"),
    ("index", "000852.SH", "中证1000"),
    ("index", "932000.CSI", "中证2000"),
)
ETF_UNIVERSE = (
    ("stock", "510050.SH", "上证50ETF"),
    ("stock", "510300.SH", "沪深300ETF"),
    ("stock", "510500.SH", "中证500ETF"),
    ("stock", "512100.SH", "中证1000ETF"),
)


class MentorContextBuilder:
    def __init__(
        self,
        database: Database,
        market_repository: MarketRepository,
        screener_repository: ScreenerRepository,
        gateway: DataGateway,
    ) -> None:
        self._database = database
        self._market = market_repository
        self._screener = screener_repository
        self._gateway = gateway

    def build(self, requested_date: str, question: str, skill: MentorSkill) -> dict[str, Any]:
        context = self._gateway.trade_context(requested_date)
        trade_date = context.actual_date or requested_date
        with self._database.read() as connection:
            summary_row = self._market.latest_summary(connection, trade_date)
            summary = _payload(summary_row)
            history = [
                _summary_item(row)
                for row in self._market.summaries(connection, trade_date, 10)
            ]
            popularity = _payload(
                self._market.latest_insight_snapshot(connection, "popularity", trade_date)
            )
            dragon = _payload(
                self._market.latest_insight_snapshot(connection, "dragon-list", trade_date)
            )
            matched = self._matched_stocks(connection, trade_date, question)
            indexes = self._market_matrix(connection, INDEX_UNIVERSE)
            etfs = self._market_matrix(connection, ETF_UNIVERSE)
        result: dict[str, Any] = {
            "data_trade_date": str(summary_row["trade_date"]) if summary_row else trade_date,
            "data_profile": skill.profile,
            "overview": summary.get("overview") or {},
            "sentiment": summary.get("sentiment") or {},
            "recent_market_history": history,
            "question_matched_stocks": matched,
        }
        self._apply_profile(result, skill.profile, summary, popularity, indexes, etfs)
        if _requires_dragon_context(question):
            result["dragon_list"] = _dragon_context(dragon, matched)
        return result

    def _matched_stocks(
        self, connection, trade_date: str, question: str
    ) -> list[dict[str, Any]]:
        directory = self._gateway.stock_directory()
        identifiers = _matched_identifiers(directory, question)
        snapshot = self._screener.latest_factor_snapshot(connection, trade_date)
        factor_rows = (
            {
                row["identifier"]: row
                for row in self._screener.factor_rows(connection, int(snapshot["id"]))
            }
            if snapshot
            else {}
        )
        result = []
        for identifier in identifiers[:2]:
            row = dict(factor_rows.get(identifier) or directory.get(identifier) or {})
            chart = self._market.chart(connection, "stock", identifier, "day")
            row["recent_prices"] = (_payload(chart).get("points") or [])[-20:]
            result.append(row)
        return result

    def _market_matrix(
        self, connection, universe: tuple[tuple[str, str, str], ...]
    ) -> list[dict[str, Any]]:
        result = []
        for entity_type, identifier, name in universe:
            row = self._market.chart(connection, entity_type, identifier, "day")
            points = _payload(row).get("points") or []
            closes = [float(item["close"]) for item in points if item.get("close")]
            result.append(
                {
                    "code": identifier,
                    "name": name,
                    "available": bool(closes),
                    "close": closes[-1] if closes else None,
                    "change": _return(closes, 1),
                    "return_5d": _return(closes, 5),
                    "return_10d": _return(closes, 10),
                    "return_20d": _return(closes, 20),
                    "amount": points[-1].get("amount") if points else None,
                }
            )
        return result

    @staticmethod
    def _apply_profile(
        result: dict[str, Any],
        profile: str,
        summary: dict[str, Any],
        popularity: dict[str, Any],
        indexes: list[dict[str, Any]],
        etfs: list[dict[str, Any]],
    ) -> None:
        limits = list(summary.get("limits") or [])
        broken = list(summary.get("broken") or [])
        yesterday = list(summary.get("yesterday_limits") or [])
        rotation = list(summary.get("sector_rotation") or [])
        ordered = sorted(
            limits,
            key=lambda row: (int(row.get("streak") or 0), float(row.get("amount") or 0)),
            reverse=True,
        )
        if profile == "first_board":
            result["first_board_environment"] = {
                "seal_rate": (summary.get("overview") or {}).get("seal_rate"),
                "first_boards": [row for row in ordered if int(row.get("streak") or 1) == 1][:35],
                "broken_stocks": broken[:30],
                "hot_sectors": rotation[:12],
            }
        elif profile == "leader":
            result.update(
                {
                    "limit_ladder": summary.get("ladders") or [],
                    "multi_board_leaders": [
                        row for row in ordered if int(row.get("streak") or 0) >= 2
                    ][:25],
                    "sector_rotation": rotation[:12],
                    "popularity_core": (popularity.get("combined") or [])[:10],
                }
            )
        elif profile == "trend":
            result.update(
                {
                    "index_momentum": indexes,
                    "sector_rotation": rotation[:20],
                    "market_breadth": summary.get("overview") or {},
                }
            )
        elif profile == "low_absorption":
            result.update(
                {
                    "yesterday_limit_performance": yesterday[:35],
                    "broken_stocks": broken[:20],
                    "hot_sectors": rotation[:12],
                }
            )
        elif profile == "macro":
            result.update(
                {
                    "broad_indexes": indexes,
                    "core_etfs": etfs,
                    "market_style": {
                        "overview": summary.get("overview") or {},
                        "top_sectors": rotation[:15],
                    },
                    "unavailable_data": [
                        "政策原文与隔夜资讯尚未接入",
                        "汇率、利率和商品宏观序列当前不可用",
                    ],
                }
            )
        else:
            result.update(
                {
                    "limit_ladder": summary.get("ladders") or [],
                    "limit_performance": summary.get("limit_performance") or [],
                    "sector_rotation": rotation[:15],
                    "limit_up_stocks": ordered[:30],
                    "broken_stocks": broken[:20],
                    "limit_down_stocks": (summary.get("down_limits") or [])[:20],
                    "yesterday_limit_performance": yesterday[:20],
                }
            )


def _payload(row) -> dict[str, Any]:
    if row is None:
        return {}
    try:
        value = json.loads(str(row["payload_json"]))
    except (KeyError, TypeError, json.JSONDecodeError):
        return {}
    return value if isinstance(value, dict) else {}


def _summary_item(row) -> dict[str, Any]:
    payload = _payload(row)
    return {
        "trade_date": str(row["trade_date"]),
        "overview": payload.get("overview") or {},
        "sentiment": payload.get("sentiment") or {},
    }


def _return(closes: list[float], days: int) -> float | None:
    if len(closes) <= days or closes[-days - 1] <= 0:
        return None
    return round((closes[-1] / closes[-days - 1] - 1) * 100, 2)


def _dragon_context(payload: dict[str, Any], matched: list[dict[str, Any]]) -> dict[str, Any]:
    codes = {
        str(item.get("code") or item.get("identifier") or "").split(".")[0]
        for item in matched
    }
    operations = payload.get("stocks") or payload.get("operations") or []
    return {
        "matched": [
            row
            for row in operations
            if str(row.get("ts_code") or row.get("code") or "").split(".")[0] in codes
        ][:20],
        "largest_net_flows": sorted(
            operations,
            key=lambda row: abs(float(row.get("net_buy") or row.get("net_million") or 0)),
            reverse=True,
        )[:12],
    }


def _requires_dragon_context(question: str) -> bool:
    return any(word in question for word in ("龙虎榜", "席位", "机构", "游资"))


def _matched_identifiers(directory: dict[str, dict[str, Any]], question: str) -> list[str]:
    codes = set(re.findall(r"(?<!\d)\d{6}(?!\d)", question))
    result = []
    for identifier, row in directory.items():
        code = str(row.get("symbol") or identifier.split(".")[0])
        name = str(row.get("name") or "")
        if code in codes or (len(name) >= 2 and name in question):
            result.append(identifier)
        if len(result) == 2:
            break
    return result
