"""因子批量注册脚本。

使用 FactorManager 批量注册因子，用户只需提供 name、desc 和表达式，
自动生成 factor_id 并保存到 factors.jsonl。

使用方法:
    1. 在 FACTORS 列表中添加因子定义
    2. 运行: uv run python src/scripts/register_factors.py

示例:
    FACTORS = [
        {
            "name": "mom_5d",
            "desc": "5日价格动量",
            "dsl": "cs_rank(close / ts_delay(close, 5) - 1)",
            "category": "momentum",  # 可选扩展字段
        },
        ...
    ]
"""

import json
import re
from pathlib import Path
from typing import Any, Dict, List, Optional

from src.factors.metadata import FactorManager
from src.factors.metadata.exceptions import DuplicateFactorError, ValidationError
from src.config.settings import get_settings


# ============================================================================
# 用户配置区域 - 在这里添加要注册的因子
# ============================================================================

FACTORS: List[Dict[str, Any]] =[
    {
        "name": "amihud_illiq_20",
        "desc": "Amihud非流动性指标(20日)：绝对收益率/成交额。该值越大，说明少量的资金就能砸盘或拉升，流动性极差，隐含极高的风险补偿预期",
        "dsl": "ts_mean(abs(pct_chg) / (amount + 1), 20)",
    },
    {
        "name": "amivest_liq_20",
        "desc": "Amivest流动性指标(20日)：Amihud的倒数变种，衡量推动1%价格变化需要的资金量。值越低，流动性溢价越高",
        "dsl": "ts_mean(amount / (abs(pct_chg) + 0.001), 20)",
    },
    {
        "name": "atr_price_impact",
        "desc": "真实波幅价格冲击(20日)：以ATR(真实波幅)代替绝对收益，剔除跳空影响后的真实交易冲击",
        "dsl": "ts_atr(high, low, close, 20) / close / (ts_mean(amount, 20) + 1)",
    },
    {
        "name": "hui_heubel_ratio",
        "desc": "Hui-Heubel流动性比率：利用高低价区间占均价的比例，除以成交额占比，捕捉阶段性区间的深度非流动性",
        "dsl": "((ts_max(high, 20) - ts_min(low, 20)) / ts_min(low, 20)) / (ts_mean(amount, 20) + 1)",
    },

    # --- 维度 2: 交易摩擦与隐形价差类 (Spread Proxies) ---
    # 逻辑：A股没有Tick级买卖价差数据，通常用日线的高低价或自协方差来推导隐形买卖价差。价差越大，交易成本越高，持有需高收益补偿。
    {
        "name": "corwin_schultz_spread_20",
        "desc": "Corwin-Schultz买卖价差代理：利用每日(最高价-最低价)/收盘价的均值衡量交易摩擦，摩擦越大的股票往往带有小盘股超额收益",
        "dsl": "ts_mean((high - low) / close, 20)",
    },
    {
        "name": "roll_spread_20",
        "desc": "Roll买卖价差代理(20日)：经典微观结构模型，计算相邻两日收益率的负协方差的平方根，反映做市商的隐形报价跳跃",
        "dsl": "sqrt(max_(-ts_cov(change, ts_delay(change, 1), 20), 0))",
    },
    {
        "name": "gibbs_effective_spread",
        "desc": "有效价差代理：使用日内振幅减去隔夜跳空幅度后的纯日内摩擦成本",
        "dsl": "ts_mean(((high - low) - abs(open - ts_delay(close, 1))) / close, 20)",
    },
    {
        "name": "overnight_illiq_20",
        "desc": "隔夜非流动性：开盘价相对于昨日收盘的跳空幅度，除以昨日成交额。隔夜极易跳空的股票具有夜间流动性溢价",
        "dsl": "ts_mean(abs(open - ts_delay(close, 1)) / (ts_delay(amount, 1) + 1), 20)",
    },

    # --- 维度 3: 流动性风险与枯竭类 (Liquidity Risk & Depletion) ---
    # 逻辑：投资者不仅讨厌“平时流动性差”，更讨厌“流动性极其不稳定”。
    {
        "name": "illiq_volatility_20",
        "desc": "Amihud非流动性的波动率(20日)：衡量价格冲击的不确定性（即流动性风险本身）。波动越大的股越容易踩踏",
        "dsl": "ts_std(abs(pct_chg) / (amount + 1), 20)",
    },
    {
        "name": "amount_cv_20",
        "desc": "成交额变异系数(20日)：成交额的波动率除以均值。反映股票被市场关注的极度不稳定性",
        "dsl": "ts_std(amount, 20) / (ts_mean(amount, 20) + 1)",
    },
    {
        "name": "amount_skewness_20",
        "desc": "成交额偏度(20日)：正偏度意味着平时成交极度清淡，偶尔脉冲式放量。这种“死鱼”状态是经典的非流动性特征",
        "dsl": "ts_skew(amount, 20)",
    },
    {
        "name": "low_vol_days_20",
        "desc": "流动性枯竭天数：过去20天内，成交额低于长期(60日)均值一半的极端缩量天数",
        "dsl": "ts_count(amount < ts_mean(amount, 60) * 0.5, 20)",
    },
    {
        "name": "liquidity_shock_momentum",
        "desc": "流动性恶化动量：近期(5日)非流动性相较于长期的变化。正值代表流动性正在迅速干涸",
        "dsl": "ts_mean(abs(pct_chg) / (amount + 1), 5) - ts_mean(abs(pct_chg) / (amount + 1), 20)",
    },

    # --- 维度 4: 非对称流动性类 (Asymmetric / Downside Illiquidity) ---
    # 逻辑：买入时没问题，但“大跌时卖不出去（流动性丧失）”是最致命的风险，这部分风险被定价的权重最高。
    {
        "name": "downside_illiq_20",
        "desc": "下行非流动性：仅在股价下跌日计算的价格冲击。捕捉‘跌时没人接盘’的极端流动性折价",
        "dsl": "ts_sum(where(change < 0, abs(change) / (amount + 1), 0), 20) / (ts_count(change < 0, 20) + 1)",
    },
    {
        "name": "upside_illiq_20",
        "desc": "上行非流动性：仅在股价上涨日计算的价格冲击。捕捉‘涨时抛压极轻’的状态",
        "dsl": "ts_sum(where(change > 0, abs(change) / (amount + 1), 0), 20) / (ts_count(change > 0, 20) + 1)",
    },
    {
        "name": "illiq_asymmetry_20",
        "desc": "非对称流动性比率：下行流动性恶化程度除以加上行流动性恶化程度。该值远大于1说明下跌时发生严重踩踏，股票本身必须折价（预期收益率补偿极高）",
        "dsl": "(ts_sum(where(change < 0, abs(change)/(amount+1), 0), 20) + 1) / (ts_sum(where(change > 0, abs(change)/(amount+1), 0), 20) + 1)",
    },
    {
        "name": "pastor_stambaugh_proxy",
        "desc": "Pastor-Stambaugh流动性贝塔代理：收益率与滞后一期带有符号(涨跌)成交额的相关性。反映市场由于流动性短缺导致的价格过度反转现象",
        "dsl": "ts_corr(change, sign(ts_delay(change, 1)) * ts_delay(amount, 1), 20)",
    },
]

# 因子存储路径（使用项目根路径下的 data 目录）
settings = get_settings()
OUTPUT_PATH = settings.data_path_resolved / "factors.jsonl"


# ============================================================================
# 核心实现
# ============================================================================


def get_next_factor_id(filepath: Path) -> str:
    """生成下一个 factor_id。

    从现有文件中提取最大序号，生成新的 F_XXX 格式 ID。

    Args:
        filepath: JSONL 文件路径

    Returns:
        新的 factor_id，如 "F_001"
    """
    if not filepath.exists():
        return "F_001"

    try:
        with open(filepath, "r", encoding="utf-8") as f:
            lines = f.readlines()
    except Exception:
        return "F_001"

    max_num = 0
    pattern = re.compile(r"^F_(\d+)$")

    for line in lines:
        line = line.strip()
        if not line:
            continue
        try:
            data = json.loads(line)
            factor_id = data.get("factor_id", "")
            match = pattern.match(factor_id)
            if match:
                num = int(match.group(1))
                max_num = max(max_num, num)
        except (json.JSONDecodeError, ValueError):
            continue

    return f"F_{max_num + 1:03d}"


def validate_factor(factor: Dict[str, Any]) -> None:
    """验证因子定义是否有效。

    Args:
        factor: 因子定义字典

    Raises:
        ValueError: 验证失败时抛出
    """
    required_fields = ["name", "desc", "dsl"]
    for field in required_fields:
        if field not in factor or not factor[field]:
            raise ValueError(f"因子缺少必填字段 '{field}'")

    # 验证 name 格式（只允许字母、数字、下划线）
    if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", factor["name"]):
        raise ValueError(
            f"因子名称 '{factor['name']}' 格式无效，只允许字母、数字、下划线"
        )


def register_factors(
    factors: List[Dict[str, Any]],
    output_path: Optional[Path] = None,
    skip_duplicates: bool = True,
) -> Dict[str, List[str]]:
    """批量注册因子。

    Args:
        factors: 因子定义列表
        output_path: 输出文件路径，默认使用 OUTPUT_PATH
        skip_duplicates: 遇到重复因子是否跳过而不是报错

    Returns:
        注册结果统计，包含成功列表和失败列表
    """
    output_path = output_path or OUTPUT_PATH
    manager = FactorManager(str(output_path))

    results = {
        "success": [],
        "failed": [],
        "skipped": [],
    }

    for factor in factors:
        try:
            # 验证因子定义
            validate_factor(factor)

            # 检查 name 是否已存在
            existing = manager.get_factors_by_name(factor["name"])
            if len(existing) > 0:
                if skip_duplicates:
                    results["skipped"].append(factor["name"])
                    print(f"[跳过] 因子 '{factor['name']}' 已存在")
                    continue
                else:
                    raise DuplicateFactorError(factor["name"])

            # 生成 factor_id
            factor_id = get_next_factor_id(output_path)

            # 构建完整的因子记录
            factor_record = {
                "factor_id": factor_id,
                "name": factor["name"],
                "desc": factor["desc"],
                "dsl": factor["dsl"],
            }

            # 添加可选扩展字段
            for key in ["category", "author", "tags", "notes"]:
                if key in factor:
                    factor_record[key] = factor[key]

            # 注册因子
            manager.add_factor(factor_record)
            results["success"].append(factor["name"])
            print(f"[成功] {factor_id}: {factor['name']}")

        except DuplicateFactorError as e:
            results["failed"].append(factor.get("name", "unknown"))
            print(f"[失败] 因子 '{factor.get('name', 'unknown')}': {e}")

        except (ValidationError, ValueError) as e:
            results["failed"].append(factor.get("name", "unknown"))
            print(f"[失败] 因子 '{factor.get('name', 'unknown')}': {e}")

        except Exception as e:
            results["failed"].append(factor.get("name", "unknown"))
            print(f"[错误] 因子 '{factor.get('name', 'unknown')}': {e}")

    return results


def main():
    """主函数。"""
    print("=" * 60)
    print("因子批量注册工具")
    print("=" * 60)
    print(f"目标文件: {OUTPUT_PATH}")
    print(f"待注册因子数: {len(FACTORS)}")
    print("-" * 60)

    if not FACTORS:
        print("[警告] FACTORS 列表为空，请在脚本中配置要注册的因子")
        return

    results = register_factors(FACTORS)

    print("-" * 60)
    print("注册完成:")
    print(f"  成功: {len(results['success'])} 个")
    print(f"  跳过: {len(results['skipped'])} 个")
    print(f"  失败: {len(results['failed'])} 个")

    if results["success"]:
        print(f"\n已注册因子: {', '.join(results['success'])}")
    if results["skipped"]:
        print(f"已跳过因子: {', '.join(results['skipped'])}")
    if results["failed"]:
        print(f"失败因子: {', '.join(results['failed'])}")


if __name__ == "__main__":
    main()
