"""
DataManager — 统一数据管理层。

策略/模型层通过 DataManager 获取数据，不直接访问 AkShare/Tushare 或数据库。
优先从 DB 读取，缺失时依次尝试 AkShare → Tushare 拉取并入库。
"""

import time

import pandas as pd

from config.settings import DEFAULT_START_DATE, DEFAULT_END_DATE
from data.sources.akshare_source import AkShareSource
from data.sources.tushare_source import TushareSource
from database import dao
from database.models import create_all_tables


class DataManager:
    """统一数据管理。双数据源：AkShare（主）+ Tushare（备）。"""

    def __init__(self):
        self._ak_source: AkShareSource | None = None
        self._ts_source: TushareSource | None = None

    @property
    def ak(self) -> AkShareSource:
        if self._ak_source is None:
            self._ak_source = AkShareSource()
        return self._ak_source

    @property
    def ts(self) -> TushareSource:
        if self._ts_source is None:
            self._ts_source = TushareSource()
        return self._ts_source

    # ── 初始化 ────────────────────────────────────────────

    def init_db(self) -> None:
        create_all_tables()

    # ── 内部：双源 try ────────────────────────────────────

    def _try_fetch(self, method_name: str, *args, **kwargs):
        """
        依次尝试 AkShare → Tushare 调用同一方法名。

        method_name: 'fetch_daily' | 'fetch_stock_list' | 'fetch_financial'
        返回: (result_df, source_name) 或 (empty_df, None)

        如果 ts_code 是指数代码，自动路由到 fetch_index_daily。
        """
        from data.sources.akshare_source import is_index_code

        # 指数自动路由
        if method_name == "fetch_daily" and args:
            ts_code = args[0]
            if is_index_code(ts_code):
                method_name = "fetch_index_daily"

        for label, source in [("Tushare", self.ts), ("AkShare", self.ak)]:
            try:
                if label == "Tushare" and not source.available:
                    continue
                fn = getattr(source, method_name)
                df = fn(*args, **kwargs)
                if df is not None and not df.empty:
                    return df, label
            except Exception as e:
                print("  [{}] {} 失败: {}".format(label, method_name, e))
        return pd.DataFrame(), None

    # ── 股票列表 ──────────────────────────────────────────

    def get_stock_list(self, force_refresh: bool = False) -> pd.DataFrame:
        if not force_refresh:
            df = dao.query_stock_list()
            if not df.empty:
                print("[DataManager] 从 DB 读取股票列表: {} 只".format(len(df)))
                return df

        print("[DataManager] 拉取股票列表 (AkShare → Tushare)...")
        df, src = self._try_fetch("fetch_stock_list")
        if df.empty:
            print("[DataManager] 所有数据源均无法获取股票列表")
            return pd.DataFrame()
        print("[DataManager] 股票列表已入库 ({}): {} 只".format(src, len(df)))
        dao.save_stock_list(df)
        time.sleep(2)
        return df

    # ── 日线数据 ──────────────────────────────────────────

    def get_daily(
        self,
        ts_code: str,
        start: str | None = None,
        end: str | None = None,
        force_refresh: bool = False,
    ) -> pd.DataFrame:
        start = start or DEFAULT_START_DATE
        end = end or DEFAULT_END_DATE or time.strftime("%Y%m%d")

        if not force_refresh:
            df = dao.query_daily(ts_code, start, end)
            if not df.empty:
                return df

        # DB 未命中 → 从数据源拉取
        df, src = self._try_fetch("fetch_daily", ts_code, start, end)
        if df.empty:
            print("[WARN] {} 日线获取失败 (AkShare+Tushare 均不可用)".format(ts_code))
            return pd.DataFrame()

        # 保存到 DB
        try:
            # 筛选 DB 需要的列
            cols = [c for c in dao._DAILY_COLS if c in df.columns]
            dao.save_daily(df[cols])
        except Exception as e:
            print("[WARN] 日线入库失败: {}".format(e))

        return df

    def sync_daily(self, ts_code: str) -> int:
        latest = dao.get_latest_trade_date(ts_code)
        today = time.strftime("%Y%m%d")
        if latest and latest >= today:
            print("[DataManager] {} 数据已是最新 ({})".format(ts_code, latest))
            return 0

        start = latest or DEFAULT_START_DATE
        df, src = self._try_fetch("fetch_daily", ts_code, start, today)
        if df.empty:
            print("[WARN] {} sync_daily 失败 (AkShare+Tushare 均不可用)".format(ts_code))
            return 0

        cols = [c for c in dao._DAILY_COLS if c in df.columns]
        dao.save_daily(df[cols])
        print("[DataManager] {} 同步 {} 条日线 ({})".format(ts_code, len(df), src))
        return len(df)

    def sync_all_daily(self) -> int:
        stock_list = self.get_stock_list()
        total = 0
        for i, ts_code in enumerate(stock_list.index):
            try:
                total += self.sync_daily(ts_code)
                if (i + 1) % 50 == 0:
                    print("[DataManager] 进度: {}/{}".format(i + 1, len(stock_list)))
                    time.sleep(1)
            except Exception as e:
                print("[WARN] {} 同步失败: {}".format(ts_code, e))
        print("[DataManager] 全量同步完成，新增 {} 条".format(total))
        return total

    # ── 财务数据 ──────────────────────────────────────────

    def get_financial(self, ts_code: str) -> pd.DataFrame:
        df = dao.query_financial(ts_code)
        if not df.empty:
            return df
        df, src = self._try_fetch("fetch_financial", ts_code)
        if not df.empty:
            try:
                cols = [c for c in dao._FINA_COLS if c in df.columns]
                dao.save_financial(df[cols])
            except Exception as e:
                print("[WARN] 财务数据入库失败: {}".format(e))
        return df
