#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Benchmark Tracker: Lifecycle management with exponential decay (V43.12)

Manages benchmarks from creation through achievement/failure to archival.
Decay formula: salience(t) = e^(-t/100). Archive when salience < 0.1 (~230 days).

Usage:
    python .claude/automation/benchmark_tracker.py --list
    python .claude/automation/benchmark_tracker.py --create "description" --criteria "success criteria"
    python .claude/automation/benchmark_tracker.py --achieve BM-001
    python .claude/automation/benchmark_tracker.py --fail BM-001 --reason "why"
    python .claude/automation/benchmark_tracker.py --decay
    python .claude/automation/benchmark_tracker.py --archive
Dependencies: Python stdlib only (Law 4)
"""

import json
import math
import sys
from datetime import datetime, timezone
from pathlib import Path

# Add Utilities to path for brain_utils

from pulse.core.brain_utils import (load_config, load_json_dict, save_json,
                         BENCHMARKS_FILE)


def load_benchmarks() -> dict:
    """Load benchmarks file."""
    data = load_json_dict(BENCHMARKS_FILE)
    if not data:
        data = {
            "version": "1.0", "schema": "V43.12",
            "last_updated": datetime.now(timezone.utc).isoformat(),
            "counters": {"total": 0, "active": 0, "achieved": 0,
                         "failed": 0, "archived": 0},
            "benchmarks": [], "archived": []
        }
    return data


def save_benchmarks(data: dict):
    """Save benchmarks with updated timestamp."""
    data["last_updated"] = datetime.now(timezone.utc).isoformat()
    save_json(BENCHMARKS_FILE, data)


def compute_salience(created_date: str) -> float:
    """Compute salience: e^(-days/100)."""
    config = load_config()
    decay_const = config.get("decay", {}).get("benchmark_decay_constant", 100)
    try:
        ts = str(created_date).replace("Z", "+00:00")
        if "T" in ts:
            d = datetime.fromisoformat(ts)
        else:
            d = datetime.fromisoformat(ts).replace(tzinfo=timezone.utc)
        days = max(0, (datetime.now(timezone.utc) - d).days)
    except (ValueError, TypeError):
        days = 0
    return round(math.exp(-days / decay_const), 4)


def next_id(data: dict) -> str:
    """Generate next benchmark ID."""
    total = data.get("counters", {}).get("total", 0) + 1
    return f"BM-{total:03d}"


def update_counters(data: dict):
    """Recompute counters from current state."""
    benchmarks = data.get("benchmarks", [])
    data["counters"] = {
        "total": len(benchmarks) + len(data.get("archived", [])),
        "active": sum(1 for b in benchmarks if b.get("status") == "active"),
        "achieved": sum(1 for b in benchmarks if b.get("status") == "achieved"),
        "failed": sum(1 for b in benchmarks if b.get("status") == "failed"),
        "archived": len(data.get("archived", []))
    }


# === LIFECYCLE OPERATIONS ===

def create_benchmark(description: str, criteria: str,
                     want_id: str = None) -> dict:
    """Create a new benchmark."""
    data = load_benchmarks()
    bm_id = next_id(data)
    now = datetime.now(timezone.utc).isoformat()
    benchmark = {
        "id": bm_id,
        "description": description,
        "criteria": criteria,
        "status": "active",
        "created": now,
        "updated": now,
        "salience": 1.0,
        "want_id": want_id,
        "outcome": None
    }
    data.setdefault("benchmarks", []).append(benchmark)
    update_counters(data)
    save_benchmarks(data)
    print(f"  [CREATED] {bm_id}: {description}")
    return benchmark


def achieve_benchmark(bm_id: str, notes: str = "") -> bool:
    """Mark benchmark as achieved."""
    data = load_benchmarks()
    for bm in data.get("benchmarks", []):
        if bm["id"] == bm_id and bm["status"] == "active":
            bm["status"] = "achieved"
            bm["updated"] = datetime.now(timezone.utc).isoformat()
            bm["outcome"] = {
                "result": "achieved",
                "notes": notes,
                "date": datetime.now(timezone.utc).isoformat()
            }
            update_counters(data)
            save_benchmarks(data)
            print(f"  [ACHIEVED] {bm_id}: {bm['description']}")
            return True
    print(f"  [ERROR] {bm_id} not found or not active")
    return False


def fail_benchmark(bm_id: str, reason: str = "") -> bool:
    """Mark benchmark as failed."""
    data = load_benchmarks()
    for bm in data.get("benchmarks", []):
        if bm["id"] == bm_id and bm["status"] == "active":
            bm["status"] = "failed"
            bm["updated"] = datetime.now(timezone.utc).isoformat()
            bm["outcome"] = {
                "result": "failed",
                "reason": reason,
                "date": datetime.now(timezone.utc).isoformat()
            }
            update_counters(data)
            save_benchmarks(data)
            print(f"  [FAILED] {bm_id}: {reason}")
            return True
    print(f"  [ERROR] {bm_id} not found or not active")
    return False


def apply_decay() -> list:
    """Apply salience decay to all active benchmarks."""
    data = load_benchmarks()
    decayed = []
    for bm in data.get("benchmarks", []):
        old_sal = bm.get("salience", 1.0)
        new_sal = compute_salience(bm.get("created", ""))
        if new_sal != old_sal:
            bm["salience"] = new_sal
            decayed.append({
                "id": bm["id"],
                "old": old_sal,
                "new": new_sal,
                "description": bm["description"][:60]
            })
    if decayed:
        save_benchmarks(data)
    return decayed


def archive_stale() -> list:
    """Move benchmarks below salience threshold to archive."""
    config = load_config()
    threshold = config.get("decay", {}).get("benchmark_archive_threshold", 0.1)
    data = load_benchmarks()
    archived = []
    remaining = []

    for bm in data.get("benchmarks", []):
        sal = compute_salience(bm.get("created", ""))
        bm["salience"] = sal
        if sal < threshold and bm.get("status") != "active":
            bm["archived_date"] = datetime.now(timezone.utc).isoformat()
            data.setdefault("archived", []).append(bm)
            archived.append({"id": bm["id"], "salience": sal,
                             "description": bm["description"][:60]})
            print(f"  [ARCHIVED] {bm['id']} (salience={sal:.4f})")
        else:
            remaining.append(bm)

    if archived:
        data["benchmarks"] = remaining
        update_counters(data)
        save_benchmarks(data)
    return archived


def list_benchmarks():
    """Display all benchmarks with current salience."""
    data = load_benchmarks()
    benchmarks = data.get("benchmarks", [])
    if not benchmarks:
        print("  No active benchmarks.")
        return
    print(f"  Total: {len(benchmarks)} benchmarks")
    for bm in benchmarks:
        sal = compute_salience(bm.get("created", ""))
        status = bm.get("status", "unknown").upper()
        desc = bm.get("description", "")[:60]
        print(f"  [{status}] {bm['id']} (salience={sal:.3f}): {desc}")


# === CLI ===

def main():
    print("=== Benchmark Tracker V43.12 ===")

    if "--list" in sys.argv:
        list_benchmarks()
        return

    if "--create" in sys.argv:
        idx = sys.argv.index("--create")
        desc = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else "Unnamed"
        criteria = ""
        if "--criteria" in sys.argv:
            ci = sys.argv.index("--criteria")
            criteria = sys.argv[ci + 1] if ci + 1 < len(sys.argv) else ""
        want_id = None
        if "--want" in sys.argv:
            wi = sys.argv.index("--want")
            want_id = sys.argv[wi + 1] if wi + 1 < len(sys.argv) else None
        create_benchmark(desc, criteria, want_id)
        return

    if "--achieve" in sys.argv:
        idx = sys.argv.index("--achieve")
        bm_id = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""
        notes = ""
        if "--notes" in sys.argv:
            ni = sys.argv.index("--notes")
            notes = sys.argv[ni + 1] if ni + 1 < len(sys.argv) else ""
        achieve_benchmark(bm_id, notes)
        return

    if "--fail" in sys.argv:
        idx = sys.argv.index("--fail")
        bm_id = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""
        reason = ""
        if "--reason" in sys.argv:
            ri = sys.argv.index("--reason")
            reason = sys.argv[ri + 1] if ri + 1 < len(sys.argv) else ""
        fail_benchmark(bm_id, reason)
        return

    if "--decay" in sys.argv:
        decayed = apply_decay()
        print(f"  Decayed: {len(decayed)} benchmarks")
        for d in decayed:
            print(f"    {d['id']}: {d['old']:.3f} -> {d['new']:.3f}")
        return

    if "--archive" in sys.argv:
        archived = archive_stale()
        print(f"  Archived: {len(archived)} benchmarks")
        return

    print("  Usage: --list | --create | --achieve | --fail | --decay | --archive")


if __name__ == "__main__":
    main()
