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

from datetime import datetime, timezone
from pathlib import Path
import logging

from pulse.core.brain_utils import load_json_dict, atomic_update_json, now_iso as _now_iso, STATE_DIR
from pulse.daemons.neural.prediction_metrics import update_metrics, alert_matches, resolve_task_title

log = logging.getLogger("pulse.daemons.neural.prediction_feedback")

ALERTS_FILE = STATE_DIR / "prediction_alerts.json"
METRICS_FILE = STATE_DIR / "prediction_metrics.json"

DECAY_AMOUNT = 0.05
EXPIRE_THRESHOLD = 0.15


def _record_reward(task_id: str, confidence: float, agent: str):
    """Fallback if reward_signal system is unavailable."""
    log.info(f"[REWARD] Fallback: task={task_id} conf={confidence} agent={agent}")


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

    updated = [False]
    alert_confidence = [0.5]

    def _updater(data):
        if not isinstance(data, dict):
            return data
        for alert in data.get("alerts", []):
            if alert_matches(alert, task_id, task_title) 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.15)
                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.15)
                try:  # P10-32: Counterfactual regret
                    if not success and float(alert.get("confidence", 0)) < 0.65:
                        regret = 1.0 - float(alert.get("confidence", 0))
                        alert["confidence"] = min(0.9, alert.get("confidence", 0.5) + regret * 0.3)
                        alert["_counterfactual_regret"] = True
                except Exception:
                    pass
                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})

    try:
        from pulse.brain.metacognition.prediction_calibration import record_domain_prediction
        _alerts_snap = load_json_dict(ALERTS_FILE)
        for _alert in _alerts_snap.get("alerts", []):
            if alert_matches(_alert, task_id, task_title) and _alert.get("outcome") in ("true_positive", "false_positive"):
                _prediction_correct = not success
                record_domain_prediction(_alert.get("domain", "general"), float(_alert.get("confidence", 0.5)), _prediction_correct)
                break
    except Exception: pass

    try:
        _alerts_data = load_json_dict(ALERTS_FILE)
        predictions = _alerts_data.get("alerts", [])
        total = len(predictions)
        correct = sum(1 for p in predictions if p.get("outcome") in ("true_positive", "true_negative"))
        accuracy = correct / max(total, 1)
        from pulse.brain.metacognition.metamemory import _record_source_outcome
        _record_source_outcome("oracle", accuracy > 0.5)
    except Exception: pass

    if updated[0]:
        outcome = "false_positive" if success else "true_positive"
        update_metrics(METRICS_FILE, outcome, alert_confidence[0], _now_iso)
        if success:
            try:
                from pulse.core.reward.reward_signal import record_task_outcome
                record_task_outcome(task_id=task_id, agent=agent_name, success=True,
                                    domain="prediction", retrieved_item_ids=[])
            except ImportError:
                _record_reward(task_id, alert_confidence[0], agent_name)
    else:
        update_metrics(METRICS_FILE, "true_negative" if success else "missed", 0.0, _now_iso)
    return updated[0]


def decay_stale_predictions():
    """Decay confidence of predictions older than 24h."""
    now = datetime.now(timezone.utc)
    decayed, archived = [0], [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"))
                if (now - created).total_seconds() < 86400:
                    continue
                last_decayed_at = alert.get("last_decayed_at")
                if last_decayed_at:
                    last_decay_dt = datetime.fromisoformat(last_decayed_at.replace("Z", "+00:00"))
                    if (now - last_decay_dt).total_seconds() < 86400:
                        continue
                alert["confidence"] = round(max(0.0, alert.get("confidence", 0.5) - DECAY_AMOUNT), 2)
                alert["last_decayed_at"] = now.isoformat()
                decayed[0] += 1
                if alert["confidence"] < EXPIRE_THRESHOLD:
                    alert["status"] = "expired"
                    alert["outcome"] = "auto_expired_stale"
                    alert["resolved_at"] = _now_iso()
                    archived[0] += 1
            except Exception: continue
        return data

    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_domain_accuracy() -> dict:
    """Get prediction accuracy broken down by domain."""
    data = load_json_dict(METRICS_FILE)
    if not data: return {"domains": {}, "by_confidence": {}}
    alerts = load_json_dict(ALERTS_FILE)
    domain_stats: dict[str, dict] = {}
    for alert in alerts.get("alerts", []):
        outcome = alert.get("outcome", "")
        if not outcome: continue
        domain = "general"
        for comp in alert.get("risk_components", []):
            if "/" in comp:
                for p in comp.split("/"):
                    if p in ("brain", "api", "cli", "web", "daemons", "core", "admin", "graph", "workers"):
                        domain = p; break
                if domain != "general": break
        d = domain_stats.setdefault(domain, {"correct": 0, "total": 0})
        d["total"] += 1
        if outcome in ("true_positive", "true_negative"): d["correct"] += 1
    for d in domain_stats.values():
        d["accuracy"] = round(d["correct"] / d["total"], 2) if d["total"] > 0 else 0.0
    return {"domains": domain_stats, "by_confidence": data.get("accuracy_by_confidence", {})}


def get_active_predictions(task_id: str = "", task_title: 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:
        resolved_title = task_title or resolve_task_title(task_id, STATE_DIR)
        active = [a for a in active if alert_matches(a, task_id, resolved_title)]
    return active
