"""FactorMiner 本地框架集成入口。

通过纯字典配置运行因子挖掘主循环，数据计算完全复用本地 FactorEngine，
不再依赖外部数据文件(data_path)。
"""

import os
import sys
from pathlib import Path
from typing import Any, Optional

import numpy as np

import polars as pl

from src.config.settings import get_settings
from src.factorminer.agent.llm_interface import create_provider, AnthropicProvider
from src.factorminer.core.config import MiningConfig as CoreMiningConfig
from src.factorminer.core.library_io import import_from_paper, save_library
from src.factorminer.core.ralph_loop import RalphLoop
from src.factorminer.core.helix_loop import HelixLoop
from src.factorminer.evaluation.local_engine import LocalFactorEvaluator
from src.factorminer.evaluation.significance import SignificanceConfig
from src.factorminer.evaluation.stock_pool_registry import StockPoolRegistry
from src.factorminer.pool_definitions import get_default_stock_pool_definitions
from src.factorminer.utils.config import load_config
from src.training.components.filters import STFilter

RUN_CONFIG: dict = {
    # 全局开关
    "mock": False,  # True: 使用 Mock LLM（无需 API Key）
    "verbose": True,  # True: 输出 DEBUG 级别日志
    "cpu": False,  # True: 强制 CPU 后端
    # 路径
    "output_dir": "./output",  # 输出目录
    "resume": None,  # 从已有 checkpoint 恢复（可选）
    # 本地数据范围（FactorEngine 自动读取 DuckDB）
    "start_date": "20190101",  # 计算开始日期
    "end_date": "20231231",  # 计算结束日期
    "stock_codes": None,  # 可选股票列表，None 表示全量
    # 种子库
    "seed_paper_library": True,  # 是否预加载 110 Paper Factors 作为种子库
    # 股票池配置（支持多股票池评估）
    "stock_pools": {
        "enabled": True,  # 是否启用多股票池
        "provider": "default",  # "default" 使用 pool_definitions.py 中的默认定义
        "use_st_filter": True,  # 是否在构建股票池前过滤 ST 股票
        "pools": ["all", "small_cap"],  # 要启用的股票池名称列表，None 表示全部启用
    },
    # 挖掘配置（覆盖 default.yaml 中的同名项）
    "mining": {
        "max_iterations": 1,
        "target_library_size": 10,
        "correlation_threshold": 0.50,
        "ic_threshold": 0.04,
        "icir_threshold": 0.50,
        "replacement_ic_min": 0.10,
        "replacement_ic_ratio": 1.30,
        "fast_screen_assets": 100,
        "num_workers": 1,
    },
    # Helix 扩展开关
    "use_helix": True,  # True: 使用 Helix Loop（5 阶段增强模式）
    "helix": {
        "enable_knowledge_graph": True,
        "enable_embeddings": True,
        "canonicalize": True,
        "enable_auto_inventor": True,
        "auto_invention_interval": 10,
        "forgetting_lambda": 0.95,
        # 高级验证器配置（可选，默认开启完整版）
        "debate_config": {},
        "causal_config": {},
        "regime_config": {},
        "capacity_config": {},
        "significance_config": {},
    },
    # LLM 配置（mock=False 时使用）
    "llm": {
        "provider": "anthropic",
        "model": "MiniMax-M2.7",
        "api_key": None,  # 可直接配置 API Key，也可通过环境变量 ANTHROPIC_API_KEY 传入
        "temperature": 0.7,
        "max_tokens": 2048,
    },
}


def _create_llm_provider(run_cfg: dict) -> Any:
    """根据配置创建 LLM Provider。"""
    llm_cfg = run_cfg.get("llm", {})
    provider = llm_cfg.get("provider", "openai")
    model = llm_cfg.get("model", "gpt-4o")
    api_key = llm_cfg.get("api_key") or os.environ.get("OPENAI_API_KEY")
    temperature = llm_cfg.get("temperature", 0.7)
    max_tokens = llm_cfg.get("max_tokens", 2048)

    if provider == "openai":
        if not api_key:
            raise ValueError(
                "OpenAI API key 未配置。请设置 OPENAI_API_KEY 环境变量，"
                "或在 RUN_CONFIG['llm']['api_key'] 中填入 API key。"
            )
        return create_provider(
            {
                "provider": "openai",
                "model": model,
                "api_key": api_key,
                "temperature": temperature,
                "max_tokens": max_tokens,
            }
        )

    if provider == "anthropic":
        api_key = (
            llm_cfg.get("api_key")
            or get_settings().anthropic_api_key
            or os.environ.get("ANTHROPIC_API_KEY")
        )
        if not api_key:
            raise ValueError(
                "Anthropic API key 未配置。请设置 ANTHROPIC_API_KEY 环境变量，"
                "或在 config/.env.local / RUN_CONFIG['llm']['api_key'] 中填入 API key。"
            )
        return AnthropicProvider(
            model=model,
            api_key=api_key,
            use_thinking=False,
        )


def _build_core_mining_config(run_cfg: dict) -> CoreMiningConfig:
    """从 RUN_CONFIG 构建 RalphLoop 需要的 MiningConfig。"""
    mining = run_cfg.get("mining", {})

    cfg = CoreMiningConfig(
        max_iterations=mining.get("max_iterations", 3),
        target_library_size=mining.get("target_library_size", 10),
        correlation_threshold=mining.get("correlation_threshold", 0.50),
        ic_threshold=mining.get("ic_threshold", 0.04),
        replacement_ic_min=mining.get("replacement_ic_min", 0.10),
        replacement_ic_ratio=mining.get("replacement_ic_ratio", 1.30),
        fast_screen_assets=mining.get("fast_screen_assets", 100),
        num_workers=mining.get("num_workers", 1),
    )

    # research 等高级配置暂设为 None（单目标模式）
    setattr(cfg, "target_panels", None)
    setattr(cfg, "target_horizons", None)
    setattr(cfg, "benchmark_mode", "paper")
    return cfg


def _build_st_pre_filter(st_filter: STFilter, end_date: str):
    """将 STFilter 包装为 StockPoolRegistry 可接受的 pre-filter 函数。"""

    def _pre_filter(df: pl.DataFrame) -> pl.Series:
        try:
            if "trade_date" not in df.columns:
                df = df.with_columns(pl.lit(end_date).alias("trade_date"))
            filtered = st_filter.filter(df)
            return df["ts_code"].is_in(filtered["ts_code"])
        except Exception as exc:
            print(f"[WARN] ST pre-filter 失败: {exc}")
            return pl.Series([True] * len(df))

    return _pre_filter


def _build_stock_pool_registry(
    run_cfg: dict,
    st_filter: Optional[STFilter] = None,
    end_date: str = "20231231",
) -> Optional[StockPoolRegistry]:
    """根据 RUN_CONFIG 构建股票池注册表。"""
    pool_cfg = run_cfg.get("stock_pools", {})
    if not pool_cfg.get("enabled", False):
        return None

    pre_filters = []
    if pool_cfg.get("use_st_filter", False) and st_filter is not None:
        pre_filters.append(_build_st_pre_filter(st_filter, end_date))

    provider = pool_cfg.get("provider", "default")
    if provider == "default":
        definitions = get_default_stock_pool_definitions()
        pool_names = pool_cfg.get("pools")
        if pool_names is not None:
            definitions = [d for d in definitions if d.name in pool_names]
            if not definitions:
                raise ValueError(
                    f"股票池配置错误: pools={pool_names} 未匹配到任何默认股票池定义"
                )
            missing = set(pool_names) - {d.name for d in definitions}
            if missing:
                print(f"[WARN] 以下股票池未在默认定义中找到，已忽略: {sorted(missing)}")
        return StockPoolRegistry.from_definitions(definitions, pre_filters=pre_filters)

    raise ValueError(f"不支持的股票池 provider: {provider}")


def _build_helix_kwargs(run_cfg: dict) -> dict:
    """从 RUN_CONFIG 构建 HelixLoop 需要的 Phase 2 扩展配置。"""
    helix = run_cfg.get("helix", {})

    # significance_config 若为 dict，需实例化为 SignificanceConfig；空 dict 视为 None
    sig_cfg = helix.get("significance_config")
    if isinstance(sig_cfg, dict) and sig_cfg:
        significance_config = SignificanceConfig(**sig_cfg)
    else:
        significance_config = None

    return {
        "enable_knowledge_graph": helix.get("enable_knowledge_graph", True),
        "enable_embeddings": helix.get("enable_embeddings", True),
        "canonicalize": helix.get("canonicalize", True),
        "enable_auto_inventor": helix.get("enable_auto_inventor", False),
        "auto_invention_interval": helix.get("auto_invention_interval", 10),
        "forgetting_lambda": helix.get("forgetting_lambda", 0.95),
        "debate_config": helix.get("debate_config"),
        "causal_config": helix.get("causal_config"),
        "regime_config": helix.get("regime_config"),
        "capacity_config": helix.get("capacity_config"),
        "significance_config": significance_config,
    }


def main(config: dict | None = None) -> None:
    """运行因子挖掘主循环。

    Args:
        config: 运行配置字典。若未提供，则使用 RUN_CONFIG 的副本。
    """
    run_cfg = config if config is not None else RUN_CONFIG.copy()

    # ------------------------------------------------------------------
    # 1. 加载并合并 default.yaml 默认配置
    # ------------------------------------------------------------------
    try:
        cfg = load_config()
    except Exception as e:
        print(f"[WARN] 加载 default.yaml 失败，使用内置默认值: {e}")
        cfg = None

    if cfg is not None:
        mining_overrides = run_cfg.get("mining", {})
        for key, val in mining_overrides.items():
            if hasattr(cfg.mining, key):
                setattr(cfg.mining, key, val)

        llm_overrides = run_cfg.get("llm", {})
        for key, val in llm_overrides.items():
            if hasattr(cfg.llm, key):
                setattr(cfg.llm, key, val)
    else:
        cfg = run_cfg

    # ------------------------------------------------------------------
    # 2. 确定输出目录
    # ------------------------------------------------------------------
    output_dir = Path(run_cfg.get("output_dir", "./output"))
    output_dir.mkdir(parents=True, exist_ok=True)
    print(f"[main] 输出目录: {output_dir.resolve()}")

    # ------------------------------------------------------------------
    # 3. 初始化 LLM Provider
    # ------------------------------------------------------------------
    provider = _create_llm_provider(run_cfg)
    print(f"[main] LLM Provider: {provider.__class__.__name__}")

    # ------------------------------------------------------------------
    # 4. 创建本地因子评估器（FactorEngine 自动读取数据）
    # ------------------------------------------------------------------
    start_date = run_cfg.get("start_date", "20200101")
    end_date = run_cfg.get("end_date", "20201231")
    stock_codes = run_cfg.get("stock_codes")

    # 4.1 创建 evaluator（先实例化才能拿到 router）
    evaluator = LocalFactorEvaluator(
        start_date=start_date,
        end_date=end_date,
        stock_codes=stock_codes,
    )

    # 4.2 构建 STFilter 和股票池注册表
    st_filter = STFilter(data_router=evaluator.engine.router)
    stock_pool_registry = _build_stock_pool_registry(
        run_cfg, st_filter=st_filter, end_date=end_date
    )
    evaluator.stock_pool_registry = stock_pool_registry

    if stock_pool_registry is not None:
        returns = evaluator.evaluate_returns_by_pool(periods=1)
        for pool_name, pool_ret in returns.items():
            valid_assets = int((~np.isnan(pool_ret).all(axis=1)).sum())
            print(
                f"[main] 股票池 {pool_name}: {valid_assets} 只资产, "
                f"returns shape: {pool_ret.shape}"
            )
    else:
        returns = evaluator.evaluate_returns(periods=1)
        print(
            f"[main] 本地数据范围: {start_date} ~ {end_date}, "
            f"returns shape: {returns.shape}"
        )

    # ------------------------------------------------------------------
    # 5. 构建 MiningConfig
    # ------------------------------------------------------------------
    mining_cfg = _build_core_mining_config(run_cfg)
    print(
        f"[main] 挖掘配置: max_iterations={mining_cfg.max_iterations}, "
        f"target_library_size={mining_cfg.target_library_size}"
    )

    # ------------------------------------------------------------------
    # 6. 选择 Loop 类型并恢复 checkpoint（可选）
    # ------------------------------------------------------------------
    use_helix = run_cfg.get("use_helix", False)
    helix_kwargs = _build_helix_kwargs(run_cfg) if use_helix else {}
    LoopCls = HelixLoop if use_helix else RalphLoop

    if use_helix:
        print("[main] 使用 Helix Loop（5 阶段增强模式）")
    else:
        print("[main] 使用 Ralph Loop（4 阶段标准模式）")

    resume_path: Optional[str] = run_cfg.get("resume")
    if resume_path is not None and Path(resume_path).exists():
        print(f"[main] 从 checkpoint 恢复: {resume_path}")
        loop = LoopCls.resume_from(
            checkpoint_path=resume_path,
            config=mining_cfg,
            returns=returns,
            llm_provider=provider,
            evaluator=evaluator,
            **helix_kwargs,
        )
    else:
        loop = LoopCls(
            config=mining_cfg,
            returns=returns,
            llm_provider=provider,
            evaluator=evaluator,
            **helix_kwargs,
        )

    # ------------------------------------------------------------------
    # 7. 预加载 Paper Factor 作为种子库（可选）
    # ------------------------------------------------------------------
    if run_cfg.get("seed_paper_library", True):
        try:
            paper_lib = import_from_paper()
            loop.library = paper_lib
            print(f"[main] 已加载 Paper Factor 种子库: {paper_lib.size} 个因子")
        except Exception as e:
            print(f"[WARN] 加载 Paper Factor 种子库失败: {e}")

    # ------------------------------------------------------------------
    # 7.5 确保目标库大小大于当前种子库，否则循环不会执行
    # ------------------------------------------------------------------
    if loop.library.size >= mining_cfg.target_library_size:
        old_target = mining_cfg.target_library_size
        new_target = loop.library.size + 10
        setattr(mining_cfg, "target_library_size", new_target)
        print(
            f"[WARN] 当前因子库({loop.library.size})已不小于目标大小({old_target})，"
            f"自动调整 target_library_size 为 {new_target} 以启动挖掘循环"
        )

    # ------------------------------------------------------------------
    # 8. 运行挖掘循环
    # ------------------------------------------------------------------
    try:
        loop.run()
    except KeyboardInterrupt:
        print("[main] 用户中断，正在保存当前进度...")
        loop.save_session()
    except Exception as e:
        print(f"[ERROR] 挖掘循环异常: {e}")
        raise

    # ------------------------------------------------------------------
    # 9. 保存最终因子库
    # ------------------------------------------------------------------
    save_path = output_dir / "factor_library"
    try:
        save_library(loop.library, str(save_path))
        print(f"[main] 因子库已保存: {save_path}")
    except Exception as e:
        print(f"[ERROR] 保存因子库失败: {e}")

    print(f"[main] 挖掘完成。最终库大小: {loop.library.size}")


if __name__ == "__main__":
    main()
