"""数据同步基础抽象模块。

提供三层抽象结构统一所有数据同步流程：
- BaseDataSync: 所有同步类型的基础抽象类
- StockBasedSync: 按股票同步的抽象类（daily, pro_bar）
- DateBasedSync: 按日期同步的抽象类（bak_basic）

使用方式:
    # 按股票同步（daily, pro_bar）
    class DailySync(StockBasedSync):
        table_name = "daily"

        def fetch_single_stock(self, ts_code: str, start_date: str, end_date: str) -> pd.DataFrame:
            # 实现单只股票数据获取
            ...

    # 按日期同步（bak_basic）
    class BakBasicSync(DateBasedSync):
        table_name = "bak_basic"

        def fetch_single_date(self, trade_date: str) -> pd.DataFrame:
            # 实现单日数据获取
            ...
"""

from abc import ABC, abstractmethod
from typing import Optional, List, Dict
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading

import pandas as pd
from tqdm import tqdm

from src.data.client import TushareClient
from src.data.storage import ThreadSafeStorage, Storage
from src.data.sync_logger import SyncLogManager
from src.data.sync_config import get_threads
from src.data.utils import get_today_date, get_next_date
from src.data.api_wrappers.api_trade_cal import (
    get_first_trading_day,
    get_last_trading_day,
    sync_trade_cal_cache,
)

from src.data.api_wrappers.api_stock_basic import _get_csv_path, sync_all_stocks


class BaseDataSync(ABC):
    """数据同步基础抽象类。

    提供所有同步类型共有的功能：
    - 客户端初始化
    - 股票代码获取
    - 交易日历操作
    - 同步需求检查
    - 缓存管理

    Attributes:
        table_name: 子类必须定义的目标表名
        DEFAULT_START_DATE: 默认同步起始日期
    """

    table_name: str = ""  # 子类必须覆盖
    DEFAULT_START_DATE = "20180101"

    # 表结构定义（子类可覆盖）
    # 格式: {"column_name": "SQL_TYPE", ...}
    TABLE_SCHEMA: Dict[str, str] = {}

    # 索引定义（子类可覆盖）
    # 格式: [("index_name", ["col1", "col2"]), ...]
    TABLE_INDEXES: List[tuple] = []

    # 主键定义（子类可覆盖）
    # 格式: ("col1", "col2")
    PRIMARY_KEY: tuple = ()

    def __init__(self, max_workers: Optional[int] = None):
        """初始化同步管理器。

        Args:
            max_workers: 工作线程数（默认从配置读取，根据 table_name 获取）
        """
        self.storage = ThreadSafeStorage()
        # 使用 table_name 作为接口名称初始化客户端，获取特定的速率限制
        self.client = TushareClient(interface_name=self.table_name or "default")
        # 根据表名获取线程数配置
        default_workers = get_threads(self.table_name or "default")
        self.max_workers = max_workers or default_workers
        self._stop_flag = threading.Event()
        self._stop_flag.set()  # 初始为未停止状态
        self._cached_data: Optional[pd.DataFrame] = None

    def _load_cached_data(self) -> pd.DataFrame:
        """从存储加载数据（带缓存）。

        Returns:
            缓存或从存储加载的 DataFrame
        """
        if self._cached_data is None:
            self._cached_data = self.storage.load(self.table_name)
        return self._cached_data

    def clear_cache(self) -> None:
        """清除缓存的数据，强制下次访问时重新加载。"""
        self._cached_data = None

    def get_all_stock_codes(self, only_listed: bool = True) -> List[str]:
        """从本地存储获取所有股票代码。

        优先使用 stock_basic.csv 以确保包含所有股票，
        避免回测中的前视偏差。

        Args:
            only_listed: 若为 True，仅返回当前上市股票（L 状态）

        Returns:
            股票代码列表
        """
        class_name = self.__class__.__name__
        print(f"[{class_name}] Ensuring stock_basic.csv is up-to-date...")
        sync_all_stocks()

        stock_csv_path = _get_csv_path()

        if stock_csv_path.exists():
            print(f"[{class_name}] Reading stock_basic from CSV: {stock_csv_path}")
            try:
                stock_df = pd.read_csv(stock_csv_path, encoding="utf-8-sig")
                if not stock_df.empty and "ts_code" in stock_df.columns:
                    if only_listed and "list_status" in stock_df.columns:
                        listed_stocks = stock_df[stock_df["list_status"] == "L"]
                        codes = listed_stocks["ts_code"].unique().tolist()
                        total = len(stock_df["ts_code"].unique())
                        print(
                            f"[{class_name}] Found {len(codes)} listed stocks (filtered from {total} total)"
                        )
                    else:
                        codes = stock_df["ts_code"].unique().tolist()
                        print(
                            f"[{class_name}] Found {len(codes)} stock codes from stock_basic.csv"
                        )
                    return codes
                else:
                    print(
                        f"[{class_name}] stock_basic.csv exists but no ts_code column or empty"
                    )
            except Exception as e:
                print(f"[{class_name}] Error reading stock_basic.csv: {e}")

        # 回退：从目标表存储获取
        print(
            f"[{class_name}] stock_basic.csv not available, falling back to {self.table_name} data..."
        )
        cached_data = self._load_cached_data()
        if not cached_data.empty and "ts_code" in cached_data.columns:
            codes = cached_data["ts_code"].unique().tolist()
            print(
                f"[{class_name}] Found {len(codes)} stock codes from {self.table_name} data"
            )
            return codes

        print(f"[{class_name}] No stock codes found in local storage")
        return []

    def get_global_last_date(self) -> Optional[str]:
        """获取全局最后交易日期。

        Returns:
            最后交易日期字符串，若无数据则返回 None
        """
        data = self._load_cached_data()
        if data.empty or "trade_date" not in data.columns:
            return None
        return str(data["trade_date"].max())

    def get_global_first_date(self) -> Optional[str]:
        """获取全局最早交易日期。

        Returns:
            最早交易日期字符串，若无数据则返回 None
        """
        data = self._load_cached_data()
        if data.empty or "trade_date" not in data.columns:
            return None
        return str(data["trade_date"].min())

    def get_trade_calendar_bounds(
        self, start_date: str, end_date: str
    ) -> tuple[Optional[str], Optional[str]]:
        """从交易日历获取首尾交易日。

        Args:
            start_date: 开始日期（YYYYMMDD 格式）
            end_date: 结束日期（YYYYMMDD 格式）

        Returns:
            (首交易日, 尾交易日) 元组，若出错则返回 (None, None)
        """
        try:
            first_day = get_first_trading_day(start_date, end_date)
            last_day = get_last_trading_day(start_date, end_date)
            return (first_day, last_day)
        except Exception as e:
            print(f"[ERROR] Failed to get trade calendar bounds: {e}")
            return (None, None)

    def check_sync_needed(
        self,
        force_full: bool = False,
    ) -> tuple[bool, Optional[str], Optional[str], Optional[str]]:
        """基于交易日历检查是否需要同步。

        逻辑：
        - 若 force_full：需要同步，返回 (True, default_start, today)
        - 若无本地数据：需要同步，返回 (True, default_start, today)
        - 若存在本地数据：
            - 从交易日历获取最后交易日
            - 若本地最后日期 >= 日历最后日期：无需同步
            - 否则：从本地最后日期+1 到最新交易日同步

        Args:
            force_full: 若为 True，始终返回需要同步

        Returns:
            (需要同步, 起始日期, 结束日期, 本地最后日期)
        """
        class_name = self.__class__.__name__

        if force_full:
            print(f"[{class_name}] Force full sync requested")
            return (True, self.DEFAULT_START_DATE, get_today_date(), None)

        # 检查本地数据是否存在
        storage = Storage()
        table_data = (
            storage.load(self.table_name)
            if storage.exists(self.table_name)
            else pd.DataFrame()
        )

        if table_data.empty or "trade_date" not in table_data.columns:
            print(
                f"[{class_name}] No local data found for table '{self.table_name}', full sync needed"
            )
            return (True, self.DEFAULT_START_DATE, get_today_date(), None)

        # 获取本地数据最后日期
        local_last_date = str(table_data["trade_date"].max())
        print(f"[{class_name}] Local data last date: {local_last_date}")

        # 从交易日历获取最新交易日
        today = get_today_date()
        _, cal_last = self.get_trade_calendar_bounds(self.DEFAULT_START_DATE, today)

        if cal_last is None:
            print(f"[{class_name}] Failed to get trade calendar, proceeding with sync")
            return (True, self.DEFAULT_START_DATE, today, local_last_date)

        print(f"[{class_name}] Calendar last trading day: {cal_last}")

        # 比较本地最后日期与日历最后日期
        try:
            local_last_int = int(local_last_date)
            cal_last_int = int(cal_last)
            if local_last_int >= cal_last_int:
                print(
                    f"[{class_name}] Local data is up-to-date, SKIPPING sync (no tokens consumed)"
                )
                return (False, None, None, None)
        except (ValueError, TypeError) as e:
            print(f"[ERROR] Date comparison failed: {e}")

        # 需要从本地最后日期+1 同步到最新交易日
        sync_start = get_next_date(local_last_date)
        print(f"[{class_name}] Incremental sync needed from {sync_start} to {cal_last}")
        return (True, sync_start, cal_last, local_last_date)

    def _determine_sync_mode(
        self,
        force_full: bool,
        sync_start_date: str,
        local_last: Optional[str],
    ) -> str:
        """确定同步模式。

        Args:
            force_full: 是否强制全量同步
            sync_start_date: 同步起始日期
            local_last: 本地最后日期

        Returns:
            同步模式: 'full', 'incremental', 或 'partial'
        """
        if force_full:
            return "full"
        elif local_last and sync_start_date == get_next_date(local_last):
            return "incremental"
        else:
            return "partial"

    def _probe_table_and_cleanup(
        self,
        probe_data: pd.DataFrame,
        probe_description: str,
    ) -> bool:
        """探测表结构：插入样本数据、验证查询、清空表。

        该步骤仅在表不存在或为空时执行，用于：
        1. 验证表结构是否正确创建
        2. 验证数据能否正常插入和查询
        3. 确保正式同步前表处于干净状态

        Args:
            probe_data: 用于探测的样本数据
            probe_description: 探测描述（用于日志）

        Returns:
            True if 探测成功，False otherwise
        """
        class_name = self.__class__.__name__
        storage = Storage()

        if probe_data.empty:
            print(f"[{class_name}] Probe skipped: no sample data available")
            return False

        try:
            print(f"[{class_name}] Probe: {probe_description}")
            print(
                f"[{class_name}] Probe: Inserting {len(probe_data)} sample records..."
            )

            # 插入样本数据
            storage.save(self.table_name, probe_data, mode="append")

            # 验证查询
            print(f"[{class_name}] Probe: Verifying data query...")
            verification = storage._connection.execute(
                f'SELECT * FROM "{self.table_name}" LIMIT 1'
            ).fetchdf()

            if verification.empty:
                print(f"[{class_name}] Probe: FAILED - Cannot query inserted data")
                return False

            print(f"[{class_name}] Probe: Query verification OK")
            # 清空表（truncate）
            print(f"[{class_name}] Probe: Cleaning up sample data...")
            storage._connection.execute(f'DELETE FROM "{self.table_name}"')

            # 验证表已清空
            count_result = storage._connection.execute(
                f'SELECT COUNT(*) FROM "{self.table_name}"'
            ).fetchone()
            remaining = count_result[0] if count_result else -1

            if remaining == 0:
                print(f"[{class_name}] Probe: SUCCESS - Table verified and cleaned")
                return True
            else:
                print(
                    f"[{class_name}] Probe: WARNING - {remaining} rows remaining after cleanup"
                )
                return True  # 仍然继续，因为主要目的是验证结构

        except Exception as e:
            print(f"[{class_name}] Probe: FAILED - {e}")
            return False

    def _should_probe_table(self) -> bool:
        """检查是否需要进行表探测。

        仅在以下情况返回 True：
        - 表不存在
        - 表存在但为空

        Returns:
            True if 需要探测，False otherwise
        """
        storage = Storage()

        # 检查表是否存在
        if not storage.exists(self.table_name):
            return True

        # 检查表是否为空
        try:
            count_result = storage._connection.execute(
                f'SELECT COUNT(*) FROM "{self.table_name}"'
            ).fetchone()
            row_count = count_result[0] if count_result else 0
            return row_count == 0
        except Exception:
            return True  # 出错时保守处理，进行探测

    def ensure_table_exists(self) -> None:
        """确保表结构存在，如不存在则创建表和索引。

        根据类属性 TABLE_SCHEMA、TABLE_INDEXES、PRIMARY_KEY 创建表。
        子类可以覆盖此方法以自定义建表逻辑。
        """
        storage = Storage()

        if storage.exists(self.table_name):
            return

        if not self.TABLE_SCHEMA:
            print(
                f"[{self.__class__.__name__}] TABLE_SCHEMA not defined, skipping table creation"
            )
            return

        # 构建列定义
        columns_def = []
        for col_name, col_type in self.TABLE_SCHEMA.items():
            columns_def.append(f'"{col_name}" {col_type}')

        # 添加主键约束
        if self.PRIMARY_KEY:
            pk_cols = ", ".join(f'"{col}"' for col in self.PRIMARY_KEY)
            columns_def.append(f"PRIMARY KEY ({pk_cols})")

        columns_sql = ", ".join(columns_def)
        create_sql = f'CREATE TABLE IF NOT EXISTS "{self.table_name}" ({columns_sql})'

        try:
            storage._connection.execute(create_sql)
            print(f"[{self.__class__.__name__}] Created table '{self.table_name}'")
        except Exception as e:
            print(f"[{self.__class__.__name__}] Error creating table: {e}")
            raise

        # 创建索引
        for idx_name, idx_cols in self.TABLE_INDEXES:
            try:
                idx_cols_sql = ", ".join(f'"{col}"' for col in idx_cols)
                storage._connection.execute(
                    f'CREATE INDEX IF NOT EXISTS "{idx_name}" ON "{self.table_name}"({idx_cols_sql})'
                )
                print(
                    f"[{self.__class__.__name__}] Created index '{idx_name}' on {idx_cols}"
                )
            except Exception as e:
                print(
                    f"[{self.__class__.__name__}] Error creating index {idx_name}: {e}"
                )

    @abstractmethod
    def preview_sync(
        self,
        force_full: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        sample_size: int = 3,
    ) -> dict:
        """预览同步数据量和样本（子类必须实现）。"""
        pass

    @abstractmethod
    def sync_all(
        self,
        force_full: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        max_workers: Optional[int] = None,
        dry_run: bool = False,
    ) -> Dict[str, pd.DataFrame]:
        """执行同步（子类必须实现）。"""
        pass


class StockBasedSync(BaseDataSync):
    """按股票同步的抽象类。

    适用于需要按股票代码逐个获取数据的接口（如 daily, pro_bar）。
    提供多线程并发获取能力。

    子类必须实现:
        - fetch_single_stock(ts_code, start_date, end_date) -> pd.DataFrame
    """

    @abstractmethod
    def fetch_single_stock(
        self,
        ts_code: str,
        start_date: str,
        end_date: str,
    ) -> pd.DataFrame:
        """获取单只股票的数据。

        Args:
            ts_code: 股票代码
            start_date: 起始日期（YYYYMMDD）
            end_date: 结束日期（YYYYMMDD）

        Returns:
            包含股票数据的 DataFrame
        """
        pass

    def _sync_single_stock_wrapper(
        self,
        ts_code: str,
        start_date: str,
        end_date: str,
    ) -> pd.DataFrame:
        """单只股票同步的包装器（处理停止标志和异常）。

        Args:
            ts_code: 股票代码
            start_date: 起始日期
            end_date: 结束日期

        Returns:
            股票数据 DataFrame
        """
        if not self._stop_flag.is_set():
            return pd.DataFrame()

        try:
            data = self.fetch_single_stock(ts_code, start_date, end_date)
            return data
        except Exception as e:
            self._stop_flag.clear()
            print(f"[ERROR] Exception syncing {ts_code}: {e}")
            raise

    def _run_concurrent_sync(
        self,
        stock_codes: List[str],
        start_date: str,
        end_date: str,
        max_workers: Optional[int] = None,
        dry_run: bool = False,
    ) -> Dict[str, pd.DataFrame]:
        """执行多线程并发同步。

        Args:
            stock_codes: 股票代码列表
            start_date: 同步起始日期
            end_date: 同步结束日期
            max_workers: 工作线程数
            dry_run: 是否为预览模式

        Returns:
            映射 ts_code 到 DataFrame 的字典
        """
        class_name = self.__class__.__name__

        if dry_run:
            print(f"\n{'=' * 60}")
            print(f"[{class_name}] DRY RUN MODE - No data will be written")
            print(f"{'=' * 60}")
            print(f"  Would sync {len(stock_codes)} stocks")
            print(f"  Date range: {start_date} to {end_date}")
            print(f"{'=' * 60}")
            return {}

        # 为新同步重置停止标志
        self._stop_flag.set()

        results: Dict[str, pd.DataFrame] = {}
        error_occurred = False
        exception_to_raise = None

        def sync_task(ts_code: str) -> tuple[str, pd.DataFrame]:
            """每只股票的任务函数。"""
            data = self._sync_single_stock_wrapper(
                ts_code=ts_code,
                start_date=start_date,
                end_date=end_date,
            )
            return (ts_code, data)

        workers = max_workers or self.max_workers
        with ThreadPoolExecutor(max_workers=workers) as executor:
            future_to_code = {
                executor.submit(sync_task, ts_code): ts_code for ts_code in stock_codes
            }

            error_count = 0
            empty_count = 0
            success_count = 0

            pbar = tqdm(
                total=len(stock_codes), desc=f"Syncing {self.table_name} stocks"
            )

            try:
                for future in as_completed(future_to_code):
                    ts_code = future_to_code[future]

                    try:
                        _, data = future.result()
                        if data is not None and not data.empty:
                            results[ts_code] = data
                            success_count += 1
                        else:
                            empty_count += 1
                            print(
                                f"[{class_name}] Stock {ts_code}: empty data (skipped, may be delisted)"
                            )
                    except Exception as e:
                        error_occurred = True
                        exception_to_raise = e
                        print(f"\n[ERROR] Sync aborted due to exception: {e}")
                        executor.shutdown(wait=False, cancel_futures=True)
                        raise exception_to_raise

                    pbar.update(1)

            except Exception:
                error_count = 1
                print(f"[{class_name}] Sync stopped due to exception")
            finally:
                pbar.close()

        # 批量写入所有数据（仅在无错误时），使用事务确保原子性
        if results and not error_occurred:
            try:
                # 开始事务
                self.storage.begin_transaction()

                for ts_code, data in results.items():
                    if not data.empty:
                        self.storage.queue_save(self.table_name, data)
                # flush 在事务中执行
                self.storage.flush(use_transaction=False)

                # 提交事务
                self.storage.commit_transaction()
                total_rows = sum(len(df) for df in results.values())
                print(
                    f"\n[{class_name}] Saved {total_rows} rows to storage (transaction committed)"
                )
            except Exception as e:
                # 回滚事务
                self.storage.rollback_transaction()
                print(f"\n[{class_name}] Transaction rolled back due to error: {e}")
                error_occurred = True
                exception_to_raise = e

        # 打印摘要
        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Sync Summary")
        print(f"{'=' * 60}")
        print(f"  Total stocks: {len(stock_codes)}")
        print(f"  Updated: {success_count}")
        print(f"  Skipped (empty/delisted): {empty_count}")
        print(
            f"  Errors: {error_count} (aborted on first error)"
            if error_count
            else "  Errors: 0"
        )
        print(f"  Date range: {start_date} to {end_date}")
        print(f"{'=' * 60}")

        return results

    def preview_sync(
        self,
        force_full: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        sample_size: int = 3,
    ) -> dict:
        """预览同步数据量和样本。

        Args:
            force_full: 若为 True，预览全量同步
            start_date: 手动指定起始日期
            end_date: 手动指定结束日期
            sample_size: 预览用样本股票数量

        Returns:
            包含预览信息的字典
        """
        class_name = self.__class__.__name__

        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Preview Mode - Analyzing sync requirements...")
        print(f"{'=' * 60}")

        # 确保交易日历缓存是最新的
        print(f"[{class_name}] Syncing trade calendar cache...")
        sync_trade_cal_cache()

        if end_date is None:
            end_date = get_today_date()

        # 检查是否需要同步
        sync_needed, cal_start, cal_end, local_last = self.check_sync_needed(force_full)

        if not sync_needed:
            print(f"\n{'=' * 60}")
            print(f"[{class_name}] Preview Result")
            print(f"{'=' * 60}")
            print("  Sync Status: NOT NEEDED")
            print("  Reason: Local data is up-to-date with trade calendar")
            print(f"{'=' * 60}")
            return {
                "sync_needed": False,
                "stock_count": 0,
                "start_date": None,
                "end_date": None,
                "estimated_records": 0,
                "sample_data": pd.DataFrame(),
                "mode": "none",
            }

        # 使用 check_sync_needed 返回的日期
        if cal_start and cal_end:
            sync_start_date = cal_start
            end_date = cal_end
        else:
            sync_start_date = start_date or self.DEFAULT_START_DATE
            if end_date is None:
                end_date = get_today_date()

        # 确定同步模式
        mode = self._determine_sync_mode(force_full, sync_start_date, local_last)

        if mode == "full":
            print(
                f"[{class_name}] Mode: FULL SYNC from {sync_start_date} to {end_date}"
            )
        elif mode == "incremental":
            print(f"[{class_name}] Mode: INCREMENTAL SYNC (bandwidth optimized)")
            print(f"[{class_name}] Sync from: {sync_start_date} to {end_date}")
        else:
            print(f"[{class_name}] Mode: SYNC from {sync_start_date} to {end_date}")

        # 获取所有股票代码
        stock_codes = self.get_all_stock_codes()
        if not stock_codes:
            print(f"[{class_name}] No stocks found to sync")
            return {
                "sync_needed": False,
                "stock_count": 0,
                "start_date": None,
                "end_date": None,
                "estimated_records": 0,
                "sample_data": pd.DataFrame(),
                "mode": "none",
            }

        stock_count = len(stock_codes)
        print(f"[{class_name}] Total stocks to sync: {stock_count}")

        # 从前几只股票获取样本数据
        print(f"[{class_name}] Fetching sample data from {sample_size} stocks...")
        sample_data_list = []
        sample_codes = stock_codes[:sample_size]

        for ts_code in sample_codes:
            try:
                data = self.fetch_single_stock(ts_code, sync_start_date, end_date)
                if not data.empty:
                    sample_data_list.append(data)
                    print(f"  - {ts_code}: {len(data)} records")
            except Exception as e:
                print(f"  - {ts_code}: Error fetching - {e}")

        # 合并样本数据
        sample_df = (
            pd.concat(sample_data_list, ignore_index=True)
            if sample_data_list
            else pd.DataFrame()
        )

        # 基于样本估算总记录数
        if not sample_df.empty:
            avg_records_per_stock = len(sample_df) / len(sample_data_list)
            estimated_records = int(avg_records_per_stock * stock_count)
        else:
            estimated_records = 0

        # 显示预览结果
        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Preview Result")
        print(f"{'=' * 60}")
        print(f"  Sync Mode: {mode.upper()}")
        print(f"  Date Range: {sync_start_date} to {end_date}")
        print(f"  Stocks to Sync: {stock_count}")
        print(f"  Sample Stocks Checked: {len(sample_data_list)}/{sample_size}")
        print(f"  Estimated Total Records: ~{estimated_records:,}")

        if not sample_df.empty:
            print(f"\n  Sample Data Preview (first {len(sample_df)} rows):")
            print("  " + "-" * 56)
            preview_cols = [
                "ts_code",
                "trade_date",
                "open",
                "high",
                "low",
                "close",
                "vol",
            ]
            available_cols = [c for c in preview_cols if c in sample_df.columns]
            sample_display = sample_df[available_cols].head(10)
            for idx, row in sample_display.iterrows():
                print(f"    {row.to_dict()}")
            print("  " + "-" * 56)

        print(f"{'=' * 60}")

        return {
            "sync_needed": True,
            "stock_count": stock_count,
            "start_date": sync_start_date,
            "end_date": end_date,
            "estimated_records": estimated_records,
            "sample_data": sample_df,
            "mode": mode,
        }

    def sync_all(
        self,
        force_full: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        max_workers: Optional[int] = None,
        dry_run: bool = False,
    ) -> Dict[str, pd.DataFrame]:
        """同步所有股票的数据。

        Args:
            force_full: 若为 True，强制完整重载
            start_date: 手动指定起始日期
            end_date: 手动指定结束日期
            max_workers: 工作线程数
            dry_run: 若为 True，仅预览

        Returns:
            映射 ts_code 到 DataFrame 的字典
        """
        class_name = self.__class__.__name__

        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Starting {self.table_name} data sync...")
        print(f"{'=' * 60}")

        # 初始化日志管理器并记录开始
        log_manager = SyncLogManager()
        sync_mode = "full" if force_full else "incremental"
        log_entry = log_manager.start_sync(
            table_name=self.table_name,
            sync_type=sync_mode,
            date_range_start=start_date,
            date_range_end=end_date,
            metadata={"dry_run": dry_run, "max_workers": max_workers},
        )

        # 首先确保交易日历缓存是最新的
        print(f"[{class_name}] Syncing trade calendar cache...")
        sync_trade_cal_cache()

        if end_date is None:
            end_date = get_today_date()

        # 基于交易日历检查是否需要同步
        sync_needed, cal_start, cal_end, local_last = self.check_sync_needed(force_full)

        if not sync_needed:
            print(f"\n{'=' * 60}")
            print(f"[{class_name}] Sync Summary")
            print(f"{'=' * 60}")
            print("  Sync: SKIPPED (local data up-to-date with trade calendar)")
            print("  Tokens saved: 0 consumed")
            print(f"{'=' * 60}")
            return {}

        # 使用 check_sync_needed 返回的日期
        if cal_start and cal_end:
            sync_start_date = cal_start
            end_date = cal_end
        else:
            sync_start_date = start_date or self.DEFAULT_START_DATE
            if end_date is None:
                end_date = get_today_date()

        # 确定同步模式
        mode = self._determine_sync_mode(force_full, sync_start_date, local_last)

        if mode == "full":
            print(
                f"[{class_name}] Mode: FULL SYNC from {sync_start_date} to {end_date}"
            )
        elif mode == "incremental":
            print(f"[{class_name}] Mode: INCREMENTAL SYNC (bandwidth optimized)")
            print(f"[{class_name}] Sync from: {sync_start_date} to {end_date}")
        else:
            print(f"[{class_name}] Mode: SYNC from {sync_start_date} to {end_date}")

        # 获取所有股票代码
        stock_codes = self.get_all_stock_codes()
        if not stock_codes:
            print(f"[{class_name}] No stocks found to sync")
            return {}

        # 首次同步探测：验证表结构是否正常
        if self._should_probe_table():
            print(
                f"[{class_name}] Table '{self.table_name}' is empty or doesn't exist, probing..."
            )
            # 使用第一只股票的完整日期范围数据进行探测
            probe_stock = stock_codes[0]
            probe_data = self.fetch_single_stock(probe_stock, sync_start_date, end_date)
            probe_desc = f"stock={probe_stock}, range={sync_start_date} to {end_date}"
            probe_success = self._probe_table_and_cleanup(probe_data, probe_desc)

            if not probe_success:
                print(
                    f"[{class_name}] Probe failed! Stopping sync to prevent data corruption."
                )
                raise RuntimeError(
                    f"Table '{self.table_name}' probe failed. "
                    "Please check database schema and column mappings."
                )
        if self._should_probe_table():
            print(
                f"[{class_name}] Table '{self.table_name}' is empty or doesn't exist, probing..."
            )
            # 使用第一只股票的完整日期范围数据进行探测
            probe_stock = stock_codes[0]
            probe_data = self.fetch_single_stock(probe_stock, sync_start_date, end_date)
            probe_desc = f"stock={probe_stock}, range={sync_start_date} to {end_date}"
            self._probe_table_and_cleanup(probe_data, probe_desc)

        print(f"[{class_name}] Total stocks to sync: {len(stock_codes)}")
        print(f"[{class_name}] Using {max_workers or self.max_workers} worker threads")
        stock_codes = self.get_all_stock_codes()
        if not stock_codes:
            print(f"[{class_name}] No stocks found to sync")
            return {}

        print(f"[{class_name}] Total stocks to sync: {len(stock_codes)}")
        print(f"[{class_name}] Using {max_workers or self.max_workers} worker threads")

        # 执行并发同步
        try:
            results = self._run_concurrent_sync(
                stock_codes=stock_codes,
                start_date=sync_start_date,
                end_date=end_date,
                max_workers=max_workers,
                dry_run=dry_run,
            )

            # 计算同步结果统计
            total_inserted = sum(len(df) for df in results.values()) if results else 0

            # 完成日志记录
            log_manager.complete_sync(
                log_entry,
                status="success" if results else "partial",
                records_inserted=total_inserted,
                records_updated=0,
                records_deleted=0,
            )

            return results

        except Exception as e:
            # 记录失败日志
            log_manager.complete_sync(log_entry, status="failed", error_message=str(e))
            raise


class DateBasedSync(BaseDataSync):
    """按日期同步的抽象类。

    适用于需要按日期逐个获取数据的接口（如 bak_basic）。
    提供顺序遍历日期范围的能力。

    子类必须实现:
        - fetch_single_date(trade_date) -> pd.DataFrame
        - default_start_date: 类属性，指定默认起始日期
    """

    default_start_date: str = "20160101"  # 子类可覆盖

    @abstractmethod
    def fetch_single_date(self, trade_date: str) -> pd.DataFrame:
        """获取单日的数据。

        Args:
            trade_date: 交易日期（YYYYMMDD）

        Returns:
            包含当日数据的 DataFrame
        """
        pass

    def _ensure_table_schema(self, sample_data: pd.DataFrame) -> None:
        """确保表结构存在（根据样本数据创建表）。

        Args:
            sample_data: 样本数据 DataFrame
        """
        storage = Storage()

        if sample_data.empty:
            return

        # 构建列定义
        columns = []
        for col in sample_data.columns:
            dtype = str(sample_data[col].dtype)
            if col == "trade_date":
                col_type = "DATE"
            elif "int" in dtype:
                col_type = "INTEGER"
            elif "float" in dtype:
                col_type = "DOUBLE"
            else:
                col_type = "VARCHAR"
            columns.append(f'"{col}" {col_type}')

        columns_sql = ", ".join(columns)
        create_sql = f'CREATE TABLE IF NOT EXISTS "{self.table_name}" ({columns_sql}, PRIMARY KEY ("trade_date", "ts_code"))'

        try:
            storage._connection.execute(create_sql)
            print(f"[{self.__class__.__name__}] Created table '{self.table_name}'")
        except Exception as e:
            print(f"[{self.__class__.__name__}] Error creating table: {e}")

        # 创建复合索引
        try:
            storage._connection.execute(f"""
                CREATE INDEX IF NOT EXISTS "idx_{self.table_name}_date_code" 
                ON "{self.table_name}"("trade_date", "ts_code")
            """)
            print(
                f"[{self.__class__.__name__}] Created composite index on (trade_date, ts_code)"
            )
        except Exception as e:
            print(f"[{self.__class__.__name__}] Error creating index: {e}")

    def _get_sync_date_range(
        self,
        start_date: Optional[str],
        end_date: Optional[str],
        force_full: bool,
    ) -> tuple[str, str, str]:
        """确定同步日期范围。

        Args:
            start_date: 指定的起始日期
            end_date: 指定的结束日期
            force_full: 是否强制全量同步

        Returns:
            (sync_start, sync_end, mode) 元组
        """
        class_name = self.__class__.__name__
        storage = Storage()

        # 默认结束日期（使用带时间逻辑的 get_today_date，9点前返回前一天）
        if end_date is None:
            end_date = get_today_date()

        # 检查表是否存在
        table_exists = storage.exists(self.table_name)

        if not table_exists or force_full:
            # 全量同步
            sync_start = start_date or self.default_start_date
            mode = "FULL"
            print(f"[{class_name}] Mode: {mode} SYNC from {sync_start} to {end_date}")
        else:
            # 增量同步
            try:
                result = storage._connection.execute(
                    f'SELECT MAX("trade_date") FROM "{self.table_name}"'
                ).fetchone()
                last_date = result[0] if result and result[0] else None
            except Exception as e:
                print(f"[{class_name}] Error getting last date: {e}")
                last_date = None

            if last_date is None:
                sync_start = start_date or self.default_start_date
                mode = "FULL (empty table)"
            else:
                # 从最后日期+1开始
                last_date_str = str(last_date).replace("-", "")
                last_dt = datetime.strptime(last_date_str, "%Y%m%d")
                next_dt = last_dt + timedelta(days=1)
                sync_start = next_dt.strftime("%Y%m%d")
                mode = "INCREMENTAL"

            # 检查是否已最新
            if sync_start > end_date:
                print(
                    f"[{class_name}] Data is up-to-date (last: {last_date}), skipping sync"
                )
                return ("", "", "up_to_date")

            print(
                f"[{class_name}] Mode: {mode} from {sync_start} to {end_date} (last: {last_date})"
            )

        return (sync_start, end_date, mode)

    def _run_date_range_sync(
        self,
        start_date: str,
        end_date: str,
        dry_run: bool = False,
    ) -> pd.DataFrame:
        """执行日期范围的顺序同步。

        Args:
            start_date: 起始日期（YYYYMMDD）
            end_date: 结束日期（YYYYMMDD）
            dry_run: 是否为预览模式

        Returns:
            合并后的数据 DataFrame
        """
        class_name = self.__class__.__name__

        if dry_run or not start_date:
            return pd.DataFrame()

        all_data: List[pd.DataFrame] = []
        current = datetime.strptime(start_date, "%Y%m%d")
        end_dt = datetime.strptime(end_date, "%Y%m%d")

        # 计算总天数用于进度条
        total_days = (end_dt - current).days + 1
        print(f"[{class_name}] Fetching data for {total_days} days...")

        with tqdm(total=total_days, desc="Syncing dates") as pbar:
            while current <= end_dt:
                date_str = current.strftime("%Y%m%d")
                try:
                    data = self.fetch_single_date(date_str)
                    if not data.empty:
                        all_data.append(data)
                        pbar.set_postfix({"date": date_str, "records": len(data)})
                except Exception as e:
                    print(f"  {date_str}: ERROR - {e}")

                current += timedelta(days=1)
                pbar.update(1)

        if not all_data:
            print(f"[{class_name}] No data fetched")
            return pd.DataFrame()

        # 合并数据
        combined = pd.concat(all_data, ignore_index=True)

        # 转换 trade_date 为 datetime
        if "trade_date" in combined.columns:
            combined["trade_date"] = pd.to_datetime(
                combined["trade_date"], format="%Y%m%d"
            )

        print(f"[{class_name}] Total records: {len(combined)}")

        return combined

    def _save_data(self, data: pd.DataFrame, sync_start_date: str) -> int:
        """保存数据到存储（使用事务确保原子性）。

        Args:
            data: 要保存的数据
            sync_start_date: 同步起始日期（用于删除旧数据）

        Returns:
            保存的记录数
        """
        if data.empty:
            return 0

        storage = Storage(read_only=False)
        records_count = len(data)

        try:
            # 开始事务
            storage.begin_transaction()

            # 删除日期范围内的旧数据
            if sync_start_date:
                sync_start_date_fmt = pd.to_datetime(
                    sync_start_date, format="%Y%m%d"
                ).date()
                storage._connection.execute(
                    f'DELETE FROM "{self.table_name}" WHERE "trade_date" >= ?',
                    [sync_start_date_fmt],
                )

            # 保存新数据（在事务中直接保存，不使用队列）
            storage.save(self.table_name, data, mode="append")

            # 提交事务
            storage.commit_transaction()
            print(
                f"[{self.__class__.__name__}] Saved {records_count} records to DuckDB (transaction committed)"
            )
            return records_count

        except Exception as e:
            storage.rollback_transaction()
            print(
                f"[{self.__class__.__name__}] Transaction rolled back due to error: {e}"
            )
            raise

    def preview_sync(
        self,
        force_full: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        sample_size: int = 3,
    ) -> dict:
        """预览同步数据量和样本。

        Args:
            force_full: 若为 True，预览全量同步
            start_date: 手动指定起始日期
            end_date: 手动指定结束日期
            sample_size: 预览天数

        Returns:
            包含预览信息的字典
        """
        class_name = self.__class__.__name__

        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Preview Mode - Analyzing sync requirements...")
        print(f"{'=' * 60}")

        # 确定日期范围
        sync_start, sync_end, mode = self._get_sync_date_range(
            start_date, end_date, force_full
        )

        if mode == "up_to_date":
            return {
                "sync_needed": False,
                "date_count": 0,
                "start_date": None,
                "end_date": None,
                "estimated_records": 0,
                "sample_data": pd.DataFrame(),
                "mode": "none",
            }

        # 计算天数
        if sync_start and sync_end:
            start_dt = datetime.strptime(sync_start, "%Y%m%d")
            end_dt = datetime.strptime(sync_end, "%Y%m%d")
            date_count = (end_dt - start_dt).days + 1
        else:
            date_count = 0

        # 获取样本数据（取前几天）
        print(
            f"[{class_name}] Fetching sample data for {min(sample_size, date_count)} days..."
        )
        sample_data_list = []

        if sync_start and date_count > 0:
            current = datetime.strptime(sync_start, "%Y%m%d")
            for _ in range(min(sample_size, date_count)):
                date_str = current.strftime("%Y%m%d")
                try:
                    data = self.fetch_single_date(date_str)
                    if not data.empty:
                        sample_data_list.append(data)
                        print(f"  - {date_str}: {len(data)} records")
                except Exception as e:
                    print(f"  - {date_str}: Error fetching - {e}")
                current += timedelta(days=1)

        sample_df = (
            pd.concat(sample_data_list, ignore_index=True)
            if sample_data_list
            else pd.DataFrame()
        )

        # 估算总记录数
        if not sample_df.empty and date_count > 0:
            avg_records_per_day = len(sample_df) / len(sample_data_list)
            estimated_records = int(avg_records_per_day * date_count)
        else:
            estimated_records = 0

        # 显示预览结果
        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Preview Result")
        print(f"{'=' * 60}")
        print(f"  Sync Mode: {mode}")
        print(f"  Date Range: {sync_start} to {sync_end}")
        print(f"  Days to Sync: {date_count}")
        print(f"  Sample Days Checked: {len(sample_data_list)}")
        print(f"  Estimated Total Records: ~{estimated_records:,}")

        if not sample_df.empty:
            print(f"\n  Sample Data Preview:")
            print("  " + "-" * 56)
            print(f"  Columns: {list(sample_df.columns)}")
            print(f"  Rows: {len(sample_df)}")
            print("  " + "-" * 56)

        print(f"{'=' * 60}")

        return {
            "sync_needed": mode != "up_to_date",
            "date_count": date_count,
            "start_date": sync_start,
            "end_date": sync_end,
            "estimated_records": estimated_records,
            "sample_data": sample_df,
            "mode": mode.lower().replace(" ", "_"),
        }

    def sync_all(
        self,
        force_full: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        max_workers: Optional[int] = None,
        dry_run: bool = False,
    ) -> pd.DataFrame:
        """同步日期范围内的所有数据。

        Args:
            force_full: 若为 True，强制完整重载
            start_date: 手动指定起始日期
            end_date: 手动指定结束日期
            max_workers: 工作线程数（本类中不使用，保持接口一致）
            dry_run: 若为 True，仅预览

        Returns:
            同步的数据 DataFrame
        """
        class_name = self.__class__.__name__

        print(f"\n{'=' * 60}")
        print(f"[{class_name}] Starting {self.table_name} data sync...")
        print(f"{'=' * 60}")

        # 初始化日志管理器并记录开始
        log_manager = SyncLogManager()
        sync_mode = "full" if force_full else "incremental"
        log_entry = log_manager.start_sync(
            table_name=self.table_name,
            sync_type=sync_mode,
            date_range_start=start_date,
            date_range_end=end_date,
            metadata={"dry_run": dry_run},
        )

        # 确定日期范围
        sync_start, sync_end, mode = self._get_sync_date_range(
            start_date, end_date, force_full
        )

        if mode == "up_to_date":
            # 记录跳过日志
            log_manager.complete_sync(log_entry, status="success", records_inserted=0)
            return pd.DataFrame()

        if dry_run:
            print(f"\n{'=' * 60}")
            print(f"[{class_name}] DRY RUN MODE - No data will be written")
            print(f"{'=' * 60}")
            print(f"  Would sync from {sync_start} to {sync_end}")
            print(f"  Mode: {mode}")
            print(f"{'=' * 60}")
            # 记录 dry run 日志
            log_manager.complete_sync(log_entry, status="success", records_inserted=0)
            return pd.DataFrame()

        try:
            # 检查表是否存在，不存在则创建
            storage = Storage()
            if not storage.exists(self.table_name):
                print(
                    f"[{class_name}] Table '{self.table_name}' doesn't exist, creating..."
                )
                # 获取样本数据以推断 schema
                sample = self.fetch_single_date(sync_end)
                if sample.empty:
                    # 尝试另一个日期
                    sample = self.fetch_single_date("20240102")
                if not sample.empty:
                    self._ensure_table_schema(sample)
                else:
                    print(
                        f"[{class_name}] Cannot create table: no sample data available"
                    )
                    log_manager.complete_sync(
                        log_entry,
                        status="failed",
                        error_message="No sample data available",
                    )
                    return pd.DataFrame()

            # 首次同步探测：验证表结构是否正常
            if self._should_probe_table():
                print(f"[{class_name}] Table '{self.table_name}' is empty, probing...")
                # 使用最近一个交易日的完整数据进行探测
                probe_date = get_last_trading_day(sync_start, sync_end)
                if probe_date:
                    probe_data = self.fetch_single_date(probe_date)
                    probe_desc = f"date={probe_date}, all stocks"
                    self._probe_table_and_cleanup(probe_data, probe_desc)

            # 执行同步
            combined = self._run_date_range_sync(sync_start, sync_end, dry_run)

            # 保存数据（在事务中）
            records_saved = 0
            if not combined.empty:
                records_saved = self._save_data(combined, sync_start)

            # 打印摘要
            print(f"\n{'=' * 60}")
            print(f"[{class_name}] Sync Summary")
            print(f"{'=' * 60}")
            print(f"  Mode: {mode}")
            print(f"  Date range: {sync_start} to {sync_end}")
            print(f"  Records: {len(combined)}")
            print(f"{'=' * 60}")

            # 完成日志记录
            log_manager.complete_sync(
                log_entry,
                status="success",
                records_inserted=records_saved,
                records_updated=0,
                records_deleted=0,
            )

            return combined

        except Exception as e:
            # 记录失败日志
            log_manager.complete_sync(log_entry, status="failed", error_message=str(e))
            raise
