#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Volatility Tracker: Adaptive learning rate multiplier (Phase 3).

When the environment is changing fast (high RPE variance, high ingestion rate,
many new domains), learning rates should increase. When stable, they decrease.

Returns a multiplier (0.5x - 2.0x) applied to reward deltas and reconsolidation.

Based on Behrens et al. (2007) — learning the value of information in
an uncertain world: volatility modulates learning rate.
"""
import json
import logging
import math
from datetime import datetime, timezone, timedelta
from pathlib import Path

from pulse.core.paths import get_state_dir

log = logging.getLogger("pulse.volatility")

VOLATILITY_FILE = get_state_dir() / "reward" / "volatility_state.json"
MIN_MULTIPLIER = 0.5
MAX_MULTIPLIER = 2.0
DEFAULT_MULTIPLIER = 1.0


def compute_volatility() -> float:
    """Compute current environment volatility as a learning rate multiplier.

    Signals:
    1. RPE variance (high variance = uncertain environment = learn faster)
    2. Ingestion rate (many new items = changing environment)
    3. Domain novelty (new domains appearing = exploration phase)

    Returns multiplier in [0.5, 2.0].
    """
    signals = []

    # Signal 1: RPE variance from recent reward signals
    try:
        from pulse.core.reward.reward_signal import REWARD_SIGNALS_FILE
        if REWARD_SIGNALS_FILE.exists():
            data = json.loads(REWARD_SIGNALS_FILE.read_text(encoding="utf-8"))
            if isinstance(data, list) and len(data) >= 5:
                rpes = [s.get("rpe_delta", 0) for s in data[-20:]]
                if rpes:
                    mean_rpe = sum(rpes) / len(rpes)
                    var_rpe = sum((r - mean_rpe) ** 2 for r in rpes) / len(rpes)
                    # High variance → high volatility (0.5-1.5 range)
                    rpe_signal = min(1.5, 0.5 + var_rpe * 5)
                    signals.append(rpe_signal)
    except Exception:
        pass

    # Signal 2: Ingestion rate (items added in last 24h vs average)
    try:
        from pulse.core.brain_utils import LESSONS_JSONL, read_jsonl_entries
        rows = read_jsonl_entries(LESSONS_JSONL)
        now = datetime.now(timezone.utc)
        day_ago = now - timedelta(hours=24)
        recent = sum(1 for r in rows if r.get("timestamp", "") > day_ago.isoformat())
        total_rate = len(rows) / max(1, 30)  # Rough daily average over month
        if total_rate > 0:
            ingestion_signal = min(2.0, recent / max(1, total_rate))
            signals.append(ingestion_signal)
    except Exception:
        pass

    # Signal 3: Domain novelty (new domains in last 7 days)
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        week_ago = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
        new_domains = conn.execute(
            "SELECT COUNT(DISTINCT domain) FROM lessons WHERE timestamp > ?",
            (week_ago,)
        ).fetchone()[0]
        total_domains = conn.execute(
            "SELECT COUNT(DISTINCT domain) FROM lessons"
        ).fetchone()[0]
        if total_domains > 0:
            novelty = min(1.5, 0.5 + (new_domains / max(1, total_domains)))
            signals.append(novelty)
    except Exception:
        pass

    if not signals:
        return DEFAULT_MULTIPLIER

    # Average all signals, clamp to range
    avg = sum(signals) / len(signals)
    multiplier = round(max(MIN_MULTIPLIER, min(MAX_MULTIPLIER, avg)), 3)

    # Persist for monitoring
    _save_state(multiplier, signals)
    return multiplier


def get_volatility_multiplier() -> float:
    """Get cached volatility multiplier (fast path — no recomputation)."""
    try:
        if VOLATILITY_FILE.exists():
            data = json.loads(VOLATILITY_FILE.read_text(encoding="utf-8"))
            ts = data.get("computed_at", "")
            if ts:
                computed = datetime.fromisoformat(ts.replace("Z", "+00:00"))
                if (datetime.now(timezone.utc) - computed).total_seconds() < 3600:
                    return data.get("multiplier", DEFAULT_MULTIPLIER)
    except Exception:
        pass
    return compute_volatility()


def _save_state(multiplier: float, signals: list):
    """Persist volatility state for monitoring."""
    try:
        VOLATILITY_FILE.parent.mkdir(parents=True, exist_ok=True)
        VOLATILITY_FILE.write_text(json.dumps({
            "multiplier": multiplier,
            "signals": signals,
            "computed_at": datetime.now(timezone.utc).isoformat(),
        }), encoding="utf-8")
    except Exception:
        pass
