#!/usr/bin/env python3
"""
将账户方向 / 币种白名单 env 写入三所 .env(缺失则追加,已存在则 --set 时覆盖).

用法(仓库根目录):
  python scripts/sync_trade_policy_env.py
  python scripts/sync_trade_policy_env.py --dry-run
  python scripts/sync_trade_policy_env.py --apply-account-profiles
  python scripts/sync_trade_policy_env.py --set-direction binance long_only

--apply-account-profiles:币安=仅多,Gate=BTC/ETH 白名单,OKX=默认不限制.
修改后须 pm2 restart 对应实例.
"""
from __future__ import annotations

import argparse
import os
import re

REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

INSTANCES = (
    "crypto_monitor_binance",
    "crypto_monitor_okx",
    "crypto_monitor_gate",
)

COMMENT_BLOCK = [
    "# 方向限制(默认 false=双向均可;true 时按 TRADE_DIRECTION 限制,修改后须重启)",
    "# TRADE_DIRECTION=long_only | short_only | both(或 多/空/双向)",
    "# 币种白名单(默认 false=全币种可手输;true 时关键位/下单/策略仅下拉选择)",
]

DEFAULTS = {
    "TRADE_DIRECTION_RESTRICT_ENABLED": "false",
    "TRADE_DIRECTION": "both",
    "TRADE_SYMBOL_RESTRICT_ENABLED": "false",
    "TRADE_SYMBOL_WHITELIST": "BTC,ETH",
}

ACCOUNT_PROFILES = {
    "crypto_monitor_binance": {
        "TRADE_DIRECTION_RESTRICT_ENABLED": "true",
        "TRADE_DIRECTION": "long_only",
        "TRADE_SYMBOL_RESTRICT_ENABLED": "false",
        "TRADE_SYMBOL_WHITELIST": "BTC,ETH",
    },
    "crypto_monitor_gate": {
        "TRADE_DIRECTION_RESTRICT_ENABLED": "false",
        "TRADE_DIRECTION": "both",
        "TRADE_SYMBOL_RESTRICT_ENABLED": "true",
        "TRADE_SYMBOL_WHITELIST": "BTC,ETH",
    },
    "crypto_monitor_okx": {
        "TRADE_DIRECTION_RESTRICT_ENABLED": "false",
        "TRADE_DIRECTION": "both",
        "TRADE_SYMBOL_RESTRICT_ENABLED": "false",
        "TRADE_SYMBOL_WHITELIST": "BTC,ETH",
    },
}


def _parse_env(path: str) -> list[str]:
    if not os.path.isfile(path):
        return []
    with open(path, "r", encoding="utf-8", errors="ignore") as f:
        return f.read().replace("\r\n", "\n").replace("\r", "\n").splitlines()


def _env_get(lines: list[str], key: str) -> str | None:
    pat = re.compile(r"^\s*" + re.escape(key) + r"\s*=\s*(.*)\s*$")
    for line in lines:
        m = pat.match(line)
        if m:
            return m.group(1).strip().strip('"').strip("'")
    return None


def _upsert(lines: list[str], key: str, value: str) -> list[str]:
    pat = re.compile(r"^\s*" + re.escape(key) + r"\s*=")
    out: list[str] = []
    replaced = False
    for line in lines:
        if pat.match(line):
            if not replaced:
                out.append(f"{key}={value}")
                replaced = True
            continue
        out.append(line)
    if not replaced:
        if out and out[-1].strip():
            out.append("")
        out.append(f"{key}={value}")
    return out


def _insert_after(lines: list[str], anchor_key: str, insert: list[str]) -> list[str]:
    pat = re.compile(r"^\s*" + re.escape(anchor_key) + r"\s*=")
    for i, line in enumerate(lines):
        if pat.match(line):
            return lines[: i + 1] + insert + lines[i + 1 :]
    if lines and lines[-1].strip():
        return lines + [""] + insert
    return lines + insert


def sync_one(
    dir_name: str,
    values: dict[str, str],
    *,
    dry_run: bool,
    force: bool,
) -> bool:
    path = os.path.join(REPO, dir_name, ".env")
    if not os.path.isfile(path):
        print(f"skip (no .env): {dir_name}")
        return False
    lines = _parse_env(path)
    changed = False
    for key, val in values.items():
        cur = _env_get(lines, key)
        if cur is None:
            if key == "TRADE_DIRECTION_RESTRICT_ENABLED" and _env_get(
                lines, "TRADE_DIRECTION"
            ) is None:
                if COMMENT_BLOCK[0] not in "\n".join(lines):
                    lines = _insert_after(lines, "POSITION_SIZING_MODE", COMMENT_BLOCK)
            lines = _upsert(lines, key, val)
            changed = True
        elif force or cur != val:
            lines = _upsert(lines, key, val)
            changed = True
    if not changed:
        print(f"ok (unchanged): {dir_name}")
        return False
    text = "\n".join(lines).rstrip() + "\n"
    print(f"update: {dir_name}")
    for k, v in values.items():
        print(f"  {k}={v}")
    if not dry_run:
        with open(path, "w", encoding="utf-8", newline="\n") as f:
            f.write(text)
    return True


def main() -> None:
    ap = argparse.ArgumentParser(description="同步三所 trade policy env")
    ap.add_argument("--dry-run", action="store_true")
    ap.add_argument(
        "--apply-account-profiles",
        action="store_true",
        help="币安仅多,Gate BTC/ETH,OKX 默认",
    )
    ap.add_argument(
        "--defaults-only",
        action="store_true",
        help="三所均写入默认(不限制)",
    )
    ap.add_argument("--force", action="store_true", help="覆盖已有值")
    ap.add_argument("--set-direction", nargs=2, metavar=("INSTANCE", "MODE"))
    ap.add_argument("--set-symbol-whitelist", nargs=2, metavar=("INSTANCE", "SYMS"))
    args = ap.parse_args()

    if args.set_direction:
        inst, mode = args.set_direction
        if inst not in INSTANCES:
            raise SystemExit(f"unknown instance: {inst}")
        sync_one(
            inst,
            {
                "TRADE_DIRECTION_RESTRICT_ENABLED": "true",
                "TRADE_DIRECTION": mode,
            },
            dry_run=args.dry_run,
            force=True,
        )
        return

    if args.set_symbol_whitelist:
        inst, syms = args.set_symbol_whitelist
        if inst not in INSTANCES:
            raise SystemExit(f"unknown instance: {inst}")
        sync_one(
            inst,
            {
                "TRADE_SYMBOL_RESTRICT_ENABLED": "true",
                "TRADE_SYMBOL_WHITELIST": syms,
            },
            dry_run=args.dry_run,
            force=True,
        )
        return

    profiles = (
        {k: dict(DEFAULTS) for k in INSTANCES}
        if args.defaults_only
        else dict(ACCOUNT_PROFILES)
        if args.apply_account_profiles
        else {k: dict(DEFAULTS) for k in INSTANCES}
    )

    if not args.apply_account_profiles and not args.defaults_only:
        ap.print_help()
        print("\n提示:部署常用 --apply-account-profiles")
        return

    any_changed = False
    for inst in INSTANCES:
        if sync_one(inst, profiles[inst], dry_run=args.dry_run, force=args.force):
            any_changed = True
    if args.dry_run and any_changed:
        print("(dry-run, 未写入)")


if __name__ == "__main__":
    main()
