#!/usr/bin/env python3
"""
strategy_sync.py — 从服务器拉取聚合洞察并同步到本地策略参数

反馈闭环流程:
  1. 每5分钟轮询 /api/data/insights/params 获取优化参数
  2. 每1分钟轮询 /api/data/risk_alerts 获取风险预警
  3. 参数写入 effective_params.json (优先级高于 strategy_rules_v3.json)
  4. 风险预警触发本地风控动作 (减仓/清仓/暂停)

Usage:
    python strategy_sync.py --sync        # 单次同步
    python strategy_sync.py --daemon      # 持续运行 (每5分钟)
    python strategy_sync.py --alerts      # 仅检查风险预警
"""
from __future__ import annotations

import json
import os
import sys
import time
import requests
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

# ── 配置 ─────────────────────────────────────────────────────────

APP_DIR = Path(__file__).resolve().parent.parent
EFFECTIVE_PARAMS_FILE = APP_DIR / "logs" / "effective_params.json"
PARAM_SYNC_LOG = APP_DIR / "logs" / "strategy_sync.ndjson"
DATA_SERVER_URL = os.environ.get("DATA_SERVER_URL", "https://43.167.210.121:8001")
DATA_API_KEY = os.environ.get("DATA_API_KEY", "")
HEADERS = {"X-Api-Key": DATA_API_KEY} if DATA_API_KEY else {}

SYNC_INTERVAL_SEC = 300  # 5分钟
ALERT_INTERVAL_SEC = 60  # 1分钟
LAST_SYNC_FILE = APP_DIR / "logs" / ".strategy_sync_last.json"


# ── 辅助函数 ─────────────────────────────────────────────────────

def log(level: str, msg: str, **extra) -> None:
    ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    entry = {"ts": ts, "level": level, "msg": msg, **extra}
    print(f"[{ts}] [{level}] {msg}", flush=True)
    if PARAM_SYNC_LOG.parent.exists():
        with open(PARAM_SYNC_LOG, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")


def _load_json(path: Path) -> dict:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}


def _save_json(path: Path, data: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_suffix(".tmp")
    tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
    tmp.replace(path)


# ── 参数同步 ─────────────────────────────────────────────────────

def sync_params() -> dict[str, Any]:
    """
    从服务器拉取策略参数优化建议，写入 effective_params.json。
    返回同步结果。
    """
    log("INFO", "Fetching strategy params from server...")
    result = {"status": "pending", "params": {}, "applied": False, "source": "unknown"}

    try:
        resp = requests.get(
            f"{DATA_SERVER_URL}/api/data/insights/params",
            headers=HEADERS,
            timeout=10,
        )
        if resp.status_code != 200:
            log("WARN", f"Server returned {resp.status_code}: {resp.text[:200]}")
            return {**result, "status": "error", "error": f"HTTP {resp.status_code}"}

        data = resp.json()
        params = data.get("params", {})
        insights = data.get("insights", {})
        baseline = data.get("baseline", {})
        event_count = data.get("event_count", 0)
        source = data.get("source", "unknown")

        if not params:
            log("INFO", "No params from server (no data yet)")
            return {**result, "status": "ok", "note": "no_data", "source": source}

        # 读取当前 effective_params (保留未变更字段)
        current = _load_json(EFFECTIVE_PARAMS_FILE)
        new_params = {**(current.get("params", {}) if current else {}), **params}

        # 记录变更
        changes = {}
        for key, new_val in params.items():
            old_val = baseline.get(key)
            if old_val is not None and new_val != old_val:
                changes[key] = {"from": old_val, "to": new_val, "delta": new_val - old_val}

        # 写入 effective_params.json — 保留本地训练历史避免覆盖
        existing = _load_json(EFFECTIVE_PARAMS_FILE) if EFFECTIVE_PARAMS_FILE.exists() else {}
        history = existing.get("history", []) if isinstance(existing.get("history"), list) else []
        # Server insights become the 'current' entry; local training history preserved
        current_entry = {
            "source": "server_insights",
            "generated_at": data.get("generated_at", ""),
            "event_count": event_count,
            "win_rate": data.get("win_rate", 0),
            "insights": insights,
            "changes": changes,
            "params": new_params,
            "synced_at": datetime.now(timezone.utc).isoformat(),
            "ttl_sec": data.get("ttl_sec", 300),
        }
        suggestions = [
            {
                "id": "SERVER_INSIGHT",
                "param": str(key),
                "applied_value": value,
                "suggested_value": value,
                "applied": True,
                "auto_apply": False,
                "source": "server_insights",
            }
            for key, value in new_params.items()
        ]
        payload = {
            "params": new_params,
            "suggestions": suggestions,
            "current": current_entry,
            "history": history,
            "updated_at": datetime.now(timezone.utc).isoformat(),
            "source": "server_insights",
        }
        _save_json(EFFECTIVE_PARAMS_FILE, payload)

        log("INFO", f"Params synced: {len(params)} params, {len(changes)} changes, "
                    f"source={source}, events={event_count}")

        # 更新最后同步时间
        _save_json(LAST_SYNC_FILE, {
            "last_sync": datetime.now(timezone.utc).isoformat(),
            "event_count": event_count,
            "win_rate": data.get("win_rate", 0),
        })

        return {
            "status": "ok",
            "applied": bool(changes),
            "params": params,
            "changes": changes,
            "source": source,
            "event_count": event_count,
        }

    except requests.exceptions.ConnectionError as e:
        log("WARN", f"Cannot reach server: {e}")
        return {**result, "status": "network_error", "error": str(e)}
    except Exception as e:
        log("ERROR", f"Sync failed: {e}")
        return {**result, "status": "error", "error": str(e)}


# ── 风险预警处理 ─────────────────────────────────────────────────

def check_risk_alerts() -> list[dict]:
    """
    从服务器获取风险预警列表。
    返回预警列表，客户端据此执行风控动作。
    """
    log("DEBUG", "Checking risk alerts...")
    try:
        resp = requests.get(
            f"{DATA_SERVER_URL}/api/data/risk_alerts",
            headers=HEADERS,
            timeout=10,
        )
        if resp.status_code != 200:
            return []
        data = resp.json()
        alerts = data.get("alerts", [])
        if alerts:
            log("WARN", f"Server alerts: {len(alerts)}", alerts=alerts)
        return alerts
    except Exception as e:
        log("DEBUG", f"Alert check failed: {e}")
        return []


def _trigger_emergency_reduce():
    """
    紧急减仓: 写入风控状态文件，gold_trader 检测后执行。
    """
    state_file = APP_DIR / "logs" / "risk_emergency.json"
    _save_json(state_file, {
        "action": "REDUCE_POSITION",
        "triggered_by": "server_risk_alert",
        "ts": datetime.now(timezone.utc).isoformat(),
        "max_lots_multiplier": 0.5,
    })
    log("WARN", "Risk alert: REDUCE_POSITION triggered")


def _trigger_emergency_close():
    """
    紧急清仓: 写入风控状态文件，gold_trader 检测后执行。
    """
    state_file = APP_DIR / "logs" / "risk_emergency.json"
    _save_json(state_file, {
        "action": "CLOSE_ALL",
        "triggered_by": "server_risk_alert",
        "ts": datetime.now(timezone.utc).isoformat(),
    })
    log("ERROR", "Risk alert: CLOSE_ALL triggered")


def process_alerts(alerts: list[dict]) -> None:
    """处理预警列表，按严重程度执行动作。"""
    for alert in alerts:
        level = alert.get("level", "INFO")
        msg = alert.get("msg", alert.get("error", ""))
        if level in ("FATAL", "CRITICAL"):
            _trigger_emergency_close()
        elif level == "ERROR":
            if "upload_fail" in msg.lower() or "connection" in msg.lower():
                _trigger_emergency_reduce()
            else:
                _trigger_emergency_reduce()


# ── 守护进程模式 ─────────────────────────────────────────────────

def run_sync_cycle() -> dict:
    """执行一次完整同步周期: params + alerts。"""
    sync_result = sync_params()
    alerts = check_risk_alerts()
    if alerts:
        process_alerts(alerts)
    return {"params": sync_result, "alerts_count": len(alerts)}


def daemon_mode() -> None:
    """持续运行，每5分钟同步参数，每1分钟检查预警。"""
    log("INFO", "Strategy sync daemon started")
    last_params_sync = 0
    while True:
        now = time.time()
        # 每 SYNC_INTERVAL_SEC 同步参数
        if now - last_params_sync >= SYNC_INTERVAL_SEC:
            run_sync_cycle()
            last_params_sync = now
        # 每 ALERT_INTERVAL_SEC 检查预警
        check_risk_alerts()
        time.sleep(ALERT_INTERVAL_SEC)


# ── 主入口 ───────────────────────────────────────────────────────

def main() -> None:
    import argparse
    parser = argparse.ArgumentParser(description="Strategy sync from server")
    parser.add_argument("--sync", action="store_true", help="单次同步参数")
    parser.add_argument("--daemon", action="store_true", help="持续守护模式")
    parser.add_argument("--alerts", action="store_true", help="仅检查风险预警")
    args = parser.parse_args()

    if args.daemon:
        daemon_mode()
    elif args.alerts:
        alerts = check_risk_alerts()
        print(json.dumps({"alerts": alerts, "count": len(alerts)}, ensure_ascii=False, indent=2))
    elif args.sync or len(sys.argv) == 1:
        result = sync_params()
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
