#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Prediction Feedback: Outcome recording, metrics tracking, and confidence
decay for the prediction system.

Split from prediction_daemon.py for Law 7 compliance.
"""

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

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent.parent

from pulse.core.brain_utils import load_json_dict, atomic_update_json, now_iso as _now_iso

STATE_DIR = ROOT_DIR / "state"
ALERTS_FILE = STATE_DIR / "prediction_alerts.json"
METRICS_FILE = STATE_DIR / "prediction_metrics.json"
REWARD_SIGNALS_FILE = STATE_DIR / "reward_signals.json"

DECAY_AMOUNT = 0.05
EXPIRE_THRESHOLD = 0.15


def _update_metrics(outcome_type: str, confidence: float):
    """Update prediction_metrics.json with outcome classification."""
    def _updater(data):
        if not isinstance(data, dict):
            data = {"total_predictions": 0, "outcomes": {}, "accuracy_by_confidence": {}, "last_updated": ""}
        data["total_predictions"] = data.get("total_predictions", 0) + 1
        outcomes = data.setdefault("outcomes", {})
        outcomes[outcome_type] = outcomes.get(outcome_type, 0) + 1
        if confidence < 0.5:
            bucket = "0.3-0.5"
        elif confidence < 0.7:
            bucket = "0.5-0.7"
        else:
            bucket = "0.7-1.0"
        by_conf = data.setdefault("accuracy_by_confidence", {})
        bucket_data = by_conf.setdefault(bucket, {"correct": 0, "total": 0})
        bucket_data["total"] = bucket_data.get("total", 0) + 1
        if outcome_type in ("true_positive", "true_negative"):
            bucket_data["correct"] = bucket_data.get("correct", 0) + 1
        data["last_updated"] = _now_iso()
        return data
    METRICS_FILE.parent.mkdir(parents=True, exist_ok=True)
    atomic_update_json(METRICS_FILE, _updater, default={})


def record_prediction_outcome(task_id: str, success: bool, agent_name: str = "unknown"):
    """Record whether a prediction was accurate (feedback loop).

    Called by pulse_save.py after task completion:
    - prediction existed AND task failed -> true_positive (prediction correct)
    - prediction existed AND task succeeded -> false_positive (wrong, bump down)
    - no prediction AND task failed -> missed (opportunity to learn)
    - no prediction AND task succeeded -> true_negative (correctly no warning)
    """
    updated = [False]
    alert_confidence = [0.5]

    def _updater(data):
        if not isinstance(data, dict):
            return data
        for alert in data.get("alerts", []):
            if alert.get("task_id") == task_id and alert.get("status") == "active":
                alert_confidence[0] = alert.get("confidence", 0.5)
                if success:
                    alert["status"] = "resolved_success"
                    alert["outcome"] = "false_positive"
                    alert["resolved_by"] = agent_name
                    alert["resolved_at"] = _now_iso()
                    alert["confidence"] = max(0.1, alert.get("confidence", 0.5) - 0.1)
                else:
                    alert["status"] = "confirmed_failure"
                    alert["outcome"] = "true_positive"
                    alert["resolved_by"] = agent_name
                    alert["resolved_at"] = _now_iso()
                    alert["confidence"] = min(1.0, alert.get("confidence", 0.5) + 0.1)
                updated[0] = True
                break
        return data

    ALERTS_FILE.parent.mkdir(parents=True, exist_ok=True)
    atomic_update_json(ALERTS_FILE, _updater, default={"alerts": [], "total_generated": 0})

    if updated[0]:
        outcome = "false_positive" if success else "true_positive"
        _update_metrics(outcome, alert_confidence[0])
        # U4: Reward signal for false positives (Dr. Dopamine)
        if success:
            _record_reward(task_id, alert_confidence[0], agent_name)
    else:
        outcome = "true_negative" if success else "missed"
        _update_metrics(outcome, 0.0)

    return updated[0]


def _record_reward(task_id: str, prediction_confidence: float, agent_name: str):
    """Record dopamine reward signal when prediction was wrong (U4: Dr. Dopamine).

    False positives (predicted failure but task succeeded) generate reward signals.
    Higher confidence wrong = bigger learning signal. Keeps last 100 signals.
    """
    reward_score = round(prediction_confidence, 3)

    def _updater(data):
        if not isinstance(data, dict):
            data = {"signals": [], "total_rewards": 0}
        data.setdefault("signals", [])
        data["signals"].append({
            "task_id": task_id,
            "reward_score": reward_score,
            "agent": agent_name,
            "timestamp": _now_iso(),
        })
        data["signals"] = data["signals"][-100:]
        data["total_rewards"] = data.get("total_rewards", 0) + 1
        data["last_updated"] = _now_iso()
        return data

    REWARD_SIGNALS_FILE.parent.mkdir(parents=True, exist_ok=True)
    atomic_update_json(REWARD_SIGNALS_FILE, _updater, default={"signals": [], "total_rewards": 0})


def decay_stale_predictions():
    """Decay confidence of predictions older than 24h. Auto-archive below threshold."""
    now = datetime.now(timezone.utc)
    decayed = [0]
    archived = [0]

    def _updater(data):
        if not isinstance(data, dict):
            return data
        for alert in data.get("alerts", []):
            if alert.get("status") != "active":
                continue
            try:
                created = datetime.fromisoformat(alert["timestamp"].replace("Z", "+00:00"))
                age_hours = (now - created).total_seconds() / 3600
            except (KeyError, ValueError):
                continue
            if age_hours >= 24:
                alert["confidence"] = round(max(0.0, alert.get("confidence", 0.5) - DECAY_AMOUNT), 2)
                decayed[0] += 1
                if alert["confidence"] < EXPIRE_THRESHOLD:
                    alert["status"] = "expired"
                    alert["outcome"] = "auto_expired_stale"
                    alert["resolved_at"] = _now_iso()
                    archived[0] += 1
        return data

    ALERTS_FILE.parent.mkdir(parents=True, exist_ok=True)
    atomic_update_json(ALERTS_FILE, _updater, default={"alerts": [], "total_generated": 0})
    return decayed[0], archived[0]


def get_prediction_accuracy() -> dict:
    """Get prediction accuracy summary for boot briefing."""
    data = load_json_dict(METRICS_FILE)
    if not data or not data.get("total_predictions"):
        return {"accuracy": 0.0, "total": 0, "correct": 0}
    outcomes = data.get("outcomes", {})
    correct = outcomes.get("true_positive", 0) + outcomes.get("true_negative", 0)
    total = data.get("total_predictions", 0)
    return {
        "accuracy": round(correct / total, 2) if total > 0 else 0.0,
        "total": total,
        "correct": correct,
    }


def get_active_predictions(task_id: str = "") -> list:
    """Get active predictions, optionally filtered by task_id."""
    alerts_data = load_json_dict(ALERTS_FILE)
    active = [a for a in alerts_data.get("alerts", []) if a.get("status") == "active"]
    if task_id:
        active = [a for a in active if a.get("task_id") == task_id]
    return active
