#!/usr/bin/env python3
"""
一键批量应用全局策略配置（推荐配置 / 自定义配置）。

用途：
- 避免在前端逐项手动修改配置；
- 支持默认推荐值、外部 JSON 覆盖、命令行临时覆盖；
- 自动写入 global_strategy_config，并尽量同步到 Redis 配置缓存。

示例：
  python scripts/apply_recommended_config.py
  python scripts/apply_recommended_config.py --dry-run
  python scripts/apply_recommended_config.py --from config/my_batch.json
  python scripts/apply_recommended_config.py --set MIN_SIGNAL_STRENGTH=9 --set SHADOW_MODE_AUTO_APPLY=true
"""
from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List

PROJECT_ROOT = Path(__file__).resolve().parent.parent
BACKEND_DIR = PROJECT_ROOT / "backend"
if str(BACKEND_DIR) not in sys.path:
    sys.path.insert(0, str(BACKEND_DIR))
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))


# 默认推荐配置（可按你的风格长期维护）
DEFAULT_RECOMMENDED: Dict[str, Dict[str, Any]] = {
    "AUTO_TRADE_ONLY_TRENDING": {"value": True, "type": "boolean", "category": "strategy"},
    "BETA_FILTER_ENABLED": {"value": True, "type": "boolean", "category": "strategy"},
    "BETA_FILTER_THRESHOLD": {"value": -0.01, "type": "number", "category": "strategy"},
    "MIN_SIGNAL_STRENGTH": {"value": 8, "type": "number", "category": "strategy"},
    "RANGING_MARKET_SIGNAL_BOOST": {"value": 2, "type": "number", "category": "strategy"},
    "NO_OPEN_HOURS_BJ": {"value": "1,2,3,4,5,6,8,9,17,18,19", "type": "string", "category": "risk"},
    "MANUAL_BLOCKED_SYMBOLS": {"value": "", "type": "string", "category": "strategy"},
    "MANUAL_REDUCED_SYMBOLS": {
        "value": "HYPEUSDT,XANUSDT,WIFUSDT,ANKRUSDT,RIVERUSDT,POLYXUSDT,PIPPINUSDT,AINUSDT,ANIMEUSDT,COSUSDT,GUNUSDT,HYPERUSDT",
        "type": "string",
        "category": "strategy",
    },
    "MANUAL_REDUCED_SYMBOL_POSITION_FACTOR": {"value": 0.5, "type": "number", "category": "strategy"},
    "MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST": {"value": 1, "type": "number", "category": "strategy"},
    "ENTRY_PULLBACK_FILTER_ENABLED": {"value": True, "type": "boolean", "category": "strategy"},
    "ENTRY_PULLBACK_INTERVAL": {"value": "", "type": "string", "category": "strategy"},
    "ENTRY_PULLBACK_LOOKBACK_BARS": {"value": 24, "type": "number", "category": "strategy"},
    "ENTRY_PULLBACK_MIN_BARS": {"value": 5, "type": "number", "category": "strategy"},
    "ENTRY_PULLBACK_MAX_LONG_IN_RANGE": {"value": 0.58, "type": "number", "category": "strategy"},
    "ENTRY_PULLBACK_MIN_SHORT_IN_RANGE": {"value": 0.42, "type": "number", "category": "strategy"},
    "SHADOW_MODE_AUTO_APPLY": {"value": True, "type": "boolean", "category": "strategy"},
    "SHADOW_MODE_MIN_CONFIDENCE": {"value": 0.7, "type": "number", "category": "strategy"},
    "SHADOW_MODE_INCREASE_LEVERAGE_MULT": {"value": 1.3, "type": "number", "category": "strategy"},
    "SHADOW_MODE_DECREASE_LEVERAGE_MULT": {"value": 0.5, "type": "number", "category": "strategy"},
    "SHADOW_MODE_SUGGESTIONS_PATH": {"value": "config/current_suggestions.json", "type": "string", "category": "strategy"},
    "SHADOW_MODE_TRACKING_PATH": {"value": "config/shadow_mode_tracking.json", "type": "string", "category": "strategy"},
}


def parse_cli_value(raw: str) -> Any:
    v = raw.strip()
    low = v.lower()
    if low in ("true", "false"):
        return low == "true"
    try:
        if "." in v:
            return float(v)
        return int(v)
    except ValueError:
        return v


def infer_type(v: Any) -> str:
    if isinstance(v, bool):
        return "boolean"
    if isinstance(v, (int, float)):
        return "number"
    return "string"


def load_external_json(path: Path) -> Dict[str, Dict[str, Any]]:
    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)
    if isinstance(data, list):
        out: Dict[str, Dict[str, Any]] = {}
        for item in data:
            if not isinstance(item, dict) or "key" not in item:
                continue
            key = str(item["key"]).strip()
            if not key:
                continue
            out[key] = {
                "value": item.get("value"),
                "type": item.get("type") or infer_type(item.get("value")),
                "category": item.get("category") or "strategy",
                "description": item.get("description"),
            }
        return out
    if isinstance(data, dict):
        out = {}
        for key, val in data.items():
            if isinstance(val, dict) and "value" in val:
                out[key] = {
                    "value": val.get("value"),
                    "type": val.get("type") or infer_type(val.get("value")),
                    "category": val.get("category") or "strategy",
                    "description": val.get("description"),
                }
            else:
                out[key] = {
                    "value": val,
                    "type": infer_type(val),
                    "category": "strategy",
                    "description": None,
                }
        return out
    raise ValueError("外部 JSON 必须是对象或数组")


def main() -> None:
    parser = argparse.ArgumentParser(description="批量应用全局策略配置")
    parser.add_argument("--from", dest="from_path", default="", help="外部 JSON 配置文件路径")
    parser.add_argument(
        "--set",
        action="append",
        default=[],
        help="临时覆盖项，格式 KEY=VALUE，可重复多次",
    )
    parser.add_argument("--dry-run", action="store_true", help="只打印，不落库")
    parser.add_argument("--updated-by", default="apply_recommended_config", help="写入 updated_by")
    args = parser.parse_args()

    merged = dict(DEFAULT_RECOMMENDED)

    if args.from_path:
        from_path = Path(args.from_path)
        if not from_path.is_absolute():
            from_path = PROJECT_ROOT / from_path
        if not from_path.is_file():
            raise FileNotFoundError(f"未找到配置文件: {from_path}")
        ext = load_external_json(from_path)
        merged.update(ext)

    for item in args.set:
        if "=" not in item:
            raise ValueError(f"--set 参数格式错误: {item}（应为 KEY=VALUE）")
        k, raw = item.split("=", 1)
        key = k.strip()
        value = parse_cli_value(raw)
        if not key:
            raise ValueError(f"--set key 不能为空: {item}")
        base = merged.get(key, {})
        merged[key] = {
            "value": value,
            "type": base.get("type") or infer_type(value),
            "category": base.get("category") or "strategy",
            "description": base.get("description"),
        }

    print(f"将应用 {len(merged)} 个配置项（dry_run={args.dry_run}）")
    for key in sorted(merged.keys()):
        meta = merged[key]
        print(f"  - {key} = {meta.get('value')} [{meta.get('type')}/{meta.get('category')}]")

    if args.dry_run:
        return

    from database.models import GlobalStrategyConfig

    mgr = None
    try:
        import config_manager
        if hasattr(config_manager, "GlobalStrategyConfigManager"):
            mgr = config_manager.GlobalStrategyConfigManager()
    except Exception:
        mgr = None

    ok, fail = 0, 0
    for key, meta in merged.items():
        try:
            GlobalStrategyConfig.set(
                key,
                meta.get("value"),
                meta.get("type") or infer_type(meta.get("value")),
                meta.get("category") or "strategy",
                meta.get("description"),
                updated_by=args.updated_by,
            )
            if mgr is not None:
                try:
                    mgr._set_to_redis(key, meta.get("value"))
                    mgr._cache[key] = meta.get("value")
                except Exception:
                    pass
            ok += 1
        except Exception as e:
            fail += 1
            print(f"[失败] {key}: {e}")

    print(f"\n完成：成功 {ok}，失败 {fail}")
    if fail > 0:
        sys.exit(1)


if __name__ == "__main__":
    main()

