"""因子预计算入口脚本。

计算 metadata 中定义的因子，并将结果持久化到本地 HDF5 缓存。
支持默认运行（计算全部因子）、增量更新、校验与差异报告。

直接修改下方 CONFIG 中的参数后运行：
    uv run python src/data/compute_factors.py

或在 Jupyter / 其他脚本中调用：
    from src.data.compute_factors import run
    run(factor_names=["ma_5", "ma_20"], start_date="20240101", end_date="20241231")
"""

import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

from tqdm import tqdm

from src.config.settings import settings
from src.data.factor_storage import FactorStorage
from src.factors import FactorEngine
from src.factors.metadata import FactorManager

DEFAULT_FACTOR_NAMES: List[str] = []

# ============================================================
# 用户参数配置区：直接修改以下变量即可
# ============================================================
CONFIG: Dict[str, Any] = {
    # 需要计算的因子名列表。空列表表示自动读取 metadata 中全部因子。
    "factor_names": ['ma_5'],
    # metadata JSONL 文件路径，None 表示使用默认 data/factors.jsonl
    "metadata": None,
    # 计算起始日期
    "start_date": "20180101",
    # 计算结束日期，None 表示当前日期
    "end_date": None,
    # 缓存输出目录，None 表示使用默认 data/factor
    "output_dir": None,
    # 校验容忍度
    "tolerance": 1e-6,
    # 强制覆盖：即使校验失败也写入（危险开关）
    "force": False,
}


def run(
    factor_names: Optional[List[str]] = None,
    metadata: Optional[str] = None,
    start_date: str = "20180101",
    end_date: Optional[str] = None,
    output_dir: Optional[str] = None,
    tolerance: float = 1e-6,
    force: bool = False,
) -> Dict[str, Any]:
    """执行因子预计算主流程。

    Args:
        factor_names: 需要计算的因子名列表。为空时自动读取 metadata 中全部因子。
        metadata: metadata JSONL 文件路径，默认 `data/factors.jsonl`。
        start_date: 计算起始日期，默认 `20180101`。
        end_date: 计算结束日期，默认当前日期。
        output_dir: 缓存输出目录，默认 `data/factor`。
        tolerance: 校验容忍度，默认 `1e-6`。
        force: 是否强制覆盖，即使校验失败也写入。

    Returns:
        运行结果字典，包含 success/failed/skipped 列表及统计信息。
    """
    if end_date is None:
        end_date = datetime.now().strftime("%Y%m%d")

    metadata_path = (
        Path(metadata) if metadata else settings.data_path_resolved / "factors.jsonl"
    )
    output_path = (
        Path(output_dir) if output_dir else settings.data_path_resolved / "factor"
    )

    engine = FactorEngine()
    storage = FactorStorage(base_dir=output_path)
    manager = FactorManager(str(metadata_path))

    names_to_compute = (
        factor_names if factor_names else manager.get_all_factors()["name"].to_list()
    )

    if not names_to_compute:
        print("[警告] 没有需要计算的因子。")
        return {"success": [], "failed": [], "skipped": []}

    success_list: List[str] = []
    failed_list: List[Dict[str, Any]] = []

    t0 = time.time()

    for name in tqdm(names_to_compute, desc="计算因子"):
        try:
            meta_df = manager.get_factors_by_name(name)
            if len(meta_df) == 0:
                failed_list.append({"name": name, "reason": "metadata 中未找到该因子"})
                continue

            dsl = meta_df["dsl"][0]
            engine.add_factor(name, dsl)

            new_df = engine.compute([name], start_date, end_date)
            if name not in new_df.columns:
                failed_list.append({"name": name, "reason": f"计算结果中缺少列 {name}"})
                continue

            passed, stats = storage.validate(name, new_df, tolerance=tolerance)

            if not passed and not force:
                failed_list.append(
                    {
                        "name": name,
                        "reason": "校验失败",
                        "stats": stats,
                    }
                )
                print(
                    f"[警告] {name} 校验失败，max_diff={stats.get('max_abs_diff')}，跳过写入。"
                )
                continue

            if stats:
                print(
                    f"[信息] {name} 校验通过 / 强制覆盖，"
                    f"matched_rows={stats.get('matched_rows', 0)}, "
                    f"max_diff={stats.get('max_abs_diff', 0.0):.6f}, "
                    f"mean_diff={stats.get('mean_abs_diff', 0.0):.6f}"
                )

            storage.save(name, new_df)
            success_list.append(name)
        except Exception as e:
            failed_list.append({"name": name, "reason": str(e)})
            print(f"[错误] 计算 {name} 时发生异常: {e}")

    elapsed = time.time() - t0

    # 打印报告
    print("\n" + "=" * 50)
    print("因子预计算运行报告")
    print("=" * 50)
    print(f"总计因子: {len(names_to_compute)}")
    print(f"成功:     {len(success_list)}")
    print(f"失败:     {len(failed_list)}")
    print(f"耗时:     {elapsed:.2f}s")

    if failed_list:
        print("\n失败详情:")
        for item in failed_list:
            print(f"  - {item['name']}: {item['reason']}")
            if "stats" in item:
                print(f"    差异统计: {item['stats']}")

    return {
        "success": success_list,
        "failed": failed_list,
        "skipped": [],
        "elapsed": elapsed,
    }


if __name__ == "__main__":
    run(
        factor_names=CONFIG["factor_names"],
        metadata=CONFIG.get("metadata"),
        start_date=CONFIG["start_date"],
        end_date=CONFIG.get("end_date"),
        output_dir=CONFIG.get("output_dir"),
        tolerance=CONFIG["tolerance"],
        force=CONFIG["force"],
    )
