#!/usr/bin/env python3
"""
Guarded L1 Config Fixer - V2 (Threshold-based, Sector-scoped)

Only applies fixes when:
1. Evidence is from sector-scoped validation
2. Frequency exceeds threshold (default 3%)
3. Changes are logged with version bump

Usage:
    python fix_l1_configs_v2.py --apply         # Apply fixes from validation
    python fix_l1_configs_v2.py --dry-run       # Show what would change
    python fix_l1_configs_v2.py --revert SECTOR # Revert to previous version
"""

import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

CONFIGS_DIR = Path(__file__).parent.parent / "data" / "primitive_configs" / "l1"
CHANGELOG_FILE = CONFIGS_DIR / "CHANGELOG.json"

# Minimum threshold for auto-enabling (% of sector spans)
ENABLE_THRESHOLD_PCT = 3.0

# Fixes derived from sector-scoped validation (validate_l1_configs_v2.py output)
# These are the ONLY fixes that should be applied
SECTOR_SCOPED_FIXES = {
    "ENTERTAINMENT": {
        "evidence": "2,320 spans from Go Karts + Soho Club",
        "enable": [
            ("TASTE", 4.3, "Entertainment venues have concessions/food service"),
        ],
        "add_weight": [
            ("CRAFT", 1.3, "13.4% frequency but unweighted"),
        ],
        "remove_weight": [],
    },
    "FOOD_DINING": {
        "evidence": "61 spans from Fika cafe",
        "enable": [
            ("COMFORT", 9.8, "Seating/atmosphere comfort matters for cafes"),
        ],
        "add_weight": [
            ("AVAILABILITY", 1.2, "16.4% frequency but unweighted"),
        ],
        "remove_weight": [
            # Note: Small sample size (61 spans) - these may be false negatives
            # Keep weights but flag for review with more data
        ],
    },
    "AUTOMOTIVE": {
        "evidence": "1,201 spans from ClickRent car rental",
        "enable": [],  # Nothing exceeds 3% threshold
        "add_weight": [],
        "remove_weight": [
            # CONDITION, HONESTY, PROMISES, RECOVERY all have 0 appearances
            # However, may be specific to rental vs repair - keep for now
        ],
    },
}


def load_changelog() -> list[dict]:
    """Load the changelog file."""
    if CHANGELOG_FILE.exists():
        with open(CHANGELOG_FILE) as f:
            return json.load(f)
    return []


def save_changelog(entries: list[dict]) -> None:
    """Save the changelog file."""
    CHANGELOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(CHANGELOG_FILE, "w") as f:
        json.dump(entries, f, indent=2)
        f.write("\n")


def load_config(sector_code: str) -> dict[str, Any] | None:
    """Load a sector config."""
    config_path = CONFIGS_DIR / f"{sector_code.lower()}_config.json"
    if not config_path.exists():
        return None
    with open(config_path) as f:
        return json.load(f)


def save_config(sector_code: str, config: dict[str, Any]) -> None:
    """Save a sector config."""
    config_path = CONFIGS_DIR / f"{sector_code.lower()}_config.json"
    with open(config_path, "w") as f:
        json.dump(config, f, indent=2)
        f.write("\n")


def apply_fixes(sector_code: str, fixes: dict, dry_run: bool = False) -> list[str]:
    """Apply fixes to a sector config."""
    config = load_config(sector_code)
    if not config:
        return [f"❌ Config not found for {sector_code}"]

    enabled = set(config.get("enabled", []))
    disabled = set(config.get("disabled", []))
    weights = config.get("weights", {})

    changes = []
    evidence = fixes.get("evidence", "unknown")

    # Enable primitives
    for prim, pct, reason in fixes.get("enable", []):
        if pct < ENABLE_THRESHOLD_PCT:
            changes.append(f"⚠️  SKIP {prim}: {pct:.1f}% below {ENABLE_THRESHOLD_PCT}% threshold")
            continue

        if prim in disabled:
            disabled.remove(prim)
            enabled.add(prim)
            changes.append(f"✓ ENABLE {prim}: {pct:.1f}% in sector data ({reason})")
        elif prim not in enabled:
            enabled.add(prim)
            changes.append(f"✓ ADD {prim}: {pct:.1f}% in sector data ({reason})")

    # Add weights
    for prim, weight, reason in fixes.get("add_weight", []):
        if prim not in weights:
            weights[prim] = weight
            changes.append(f"⚖️  WEIGHT {prim}: {weight}x ({reason})")

    # Remove weights
    for prim, reason in fixes.get("remove_weight", []):
        if prim in weights:
            del weights[prim]
            changes.append(f"⚖️  UNWEIGHT {prim}: ({reason})")

    if not changes:
        return ["✓ No changes needed"]

    if not dry_run:
        # Bump version
        old_version = config.get("config_version", "1.0")
        major, minor = old_version.split(".")
        new_version = f"{major}.{int(minor) + 1}"

        config["enabled"] = sorted(enabled)
        config["disabled"] = sorted(disabled)
        config["weights"] = dict(sorted(weights.items()))
        config["config_version"] = new_version
        config["config_updated_at"] = datetime.now(timezone.utc).isoformat()

        save_config(sector_code, config)

        # Log to changelog
        changelog = load_changelog()
        changelog.append({
            "sector": sector_code,
            "version": new_version,
            "previous_version": old_version,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "evidence": evidence,
            "changes": changes,
        })
        save_changelog(changelog)

        changes.append(f"📝 Version: {old_version} → {new_version}")

    return changes


def revert_config(sector_code: str, to_version: str | None = None) -> list[str]:
    """Revert a config to a previous version."""
    changelog = load_changelog()

    # Find entries for this sector
    sector_entries = [e for e in changelog if e["sector"] == sector_code]
    if not sector_entries:
        return [f"❌ No changelog entries for {sector_code}"]

    # TODO: Implement actual revert by storing full config snapshots
    return [f"⚠️  Revert not yet implemented - manual restore required"]


def main():
    parser = argparse.ArgumentParser(description="Guarded L1 config fixer")
    parser.add_argument("--apply", action="store_true", help="Apply sector-scoped fixes")
    parser.add_argument("--dry-run", action="store_true", help="Show what would change")
    parser.add_argument("--revert", metavar="SECTOR", help="Revert sector to previous version")
    parser.add_argument("--sector", help="Apply to specific sector only")
    parser.add_argument("--show-changelog", action="store_true", help="Show changelog")

    args = parser.parse_args()

    if args.show_changelog:
        changelog = load_changelog()
        print(json.dumps(changelog, indent=2))
        return

    if args.revert:
        changes = revert_config(args.revert.upper())
        for change in changes:
            print(change)
        return

    if args.apply or args.dry_run:
        print("=" * 60)
        print(f"L1 CONFIG FIXER V2 - {'DRY RUN' if args.dry_run else 'APPLYING FIXES'}")
        print(f"Threshold: {ENABLE_THRESHOLD_PCT}%")
        print("=" * 60)

        sectors = [args.sector.upper()] if args.sector else SECTOR_SCOPED_FIXES.keys()

        for sector in sectors:
            if sector not in SECTOR_SCOPED_FIXES:
                print(f"\n⚠️  {sector}: No sector-scoped fixes defined")
                continue

            print(f"\n📁 {sector}")
            print(f"   Evidence: {SECTOR_SCOPED_FIXES[sector]['evidence']}")

            changes = apply_fixes(sector, SECTOR_SCOPED_FIXES[sector], dry_run=args.dry_run)
            for change in changes:
                print(f"   {change}")

        print("\n" + "=" * 60)
        if args.dry_run:
            print("DRY RUN - No changes applied")
        else:
            print("Fixes applied - see CHANGELOG.json for history")
        print("=" * 60)
        return

    parser.print_help()


if __name__ == "__main__":
    main()
