#!/usr/bin/env python3
"""
sync_params.py — Ensure all 20 active params files contain every key in config.WEIGHT_COLS.

Run this any time a new signal is added to WEIGHT_COLS in config.py.
It adds missing keys with a default unlocked range (same as BTC 4H) so they are
immediately included in the next optimization run rather than silently treated as zero.

Usage:
    python3 tools/sync_params.py          # dry run — shows what would change
    python3 tools/sync_params.py --apply  # write changes to files

Exit code 1 if any files are out of sync (useful as a pre-commit or pre-run check).
"""

import argparse
import glob
import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import WEIGHT_COLS, NON_WEIGHT_PARAMS

# Only the 20 active asset-TF params files (excludes templates/legacy files).
ACTIVE_PARAMS_GLOB = "strategies/params/params_strategy_activation_scores_*_*.json"

# Default range applied when adding a missing weight key.
# Wide symmetric range — let the optimizer decide the sign and magnitude.
DEFAULT_WEIGHT_RANGE = {"start": -100.0, "stop": 100.0, "step": 5.0}

# Default range for the i_div_window processing param (not a weight, but still tracked).
DEFAULT_DIV_WINDOW_RANGE = {"start": 1, "stop": 20, "step": 1}

# Reference file: use BTC 4H as the canonical template for default ranges.
REFERENCE_FILE = "strategies/params/params_strategy_activation_scores_COINBASE_BTCUSD_4H.json"


def get_default_for_key(key: str, reference_params: dict) -> dict:
    """Return the best default range for a missing key."""
    if key in reference_params:
        return reference_params[key]
    if key in NON_WEIGHT_PARAMS:
        return NON_WEIGHT_PARAMS[key]
    if key == "i_div_window":
        return DEFAULT_DIV_WINDOW_RANGE
    if key.startswith("i_w_"):
        return DEFAULT_WEIGHT_RANGE
    return {"values": [0]}


def main():
    parser = argparse.ArgumentParser(description="Sync all active params files to match WEIGHT_COLS in config.py.")
    parser.add_argument("--apply", action="store_true", help="Write changes to params files (default: dry run)")
    args = parser.parse_args()

    files = sorted(glob.glob(ACTIVE_PARAMS_GLOB))
    if not files:
        print("ERROR: No active params files found matching pattern:")
        print(f"  {ACTIVE_PARAMS_GLOB}")
        sys.exit(1)

    # Load reference file for canonical ranges.
    reference_params = {}
    if os.path.exists(REFERENCE_FILE):
        with open(REFERENCE_FILE) as f:
            reference_params = json.load(f)

    # Keys to check: all WEIGHT_COLS + i_div_window + NON_WEIGHT_PARAMS from config.
    all_expected_keys = list(WEIGHT_COLS) + ["i_div_window"] + list(NON_WEIGHT_PARAMS.keys())

    total_missing = 0
    files_with_issues = []

    for path in files:
        try:
            with open(path) as f:
                params = json.load(f)
        except Exception as e:
            print(f"ERROR reading {path}: {e}")
            sys.exit(1)

        missing = [k for k in all_expected_keys if k not in params]
        if not missing:
            continue

        total_missing += len(missing)
        files_with_issues.append((path, missing))
        shortname = os.path.basename(path)
        print(f"\n  {shortname}  — missing {len(missing)} key(s):")
        for k in missing:
            default = get_default_for_key(k, reference_params)
            print(f"    {k}  →  {default}")

        if args.apply:
            for k in missing:
                params[k] = get_default_for_key(k, reference_params)
            with open(path, "w") as f:
                json.dump(params, f, indent=2)

    if total_missing == 0:
        print(f"✅  All {len(files)} active params files are in sync with config.WEIGHT_COLS.")
        sys.exit(0)
    else:
        if args.apply:
            print(f"\n✅  Added {total_missing} missing key(s) across {len(files_with_issues)} file(s).")
        else:
            print(f"\n⚠️  {total_missing} missing key(s) across {len(files_with_issues)} file(s).")
            print("    Run with --apply to add them automatically.")
            print("    Or run: python3 tools/sync_params.py --apply")
        sys.exit(0 if args.apply else 1)


if __name__ == "__main__":
    main()
