# aaa_mcp/core/telemetry.py
# v64.1 — Constitutional Telemetry & Feedback Loop
# Q3 Verdict: TELEMETRY FIRST with LOCKED ADAPTATION TRIGGER

from dataclasses import dataclass, field
from typing import Dict, List, Optional
from datetime import datetime, timedelta
import json
import os

# Q3: Locked adaptation trigger
ADAPTATION_LOCK_DAYS = 30
ADAPTATION_DRIFT_THRESHOLD = 0.15  # 15% drift required


@dataclass
class ConstitutionalTelemetry:
    """
    v64.1 Telemetry-only feedback loop.

    Q3 Architectural Decision:
    - Log metrics only (no adaptation) for first 30 days
    - Calculate drift weekly
    - Adaptation requires: 30 days + drift < threshold + human approval

    This prevents noise-driven governance drift.
    """

    session_id: str
    timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())

    # L7: Gate activations
    omega_0: float = 0.0
    irreversibility_index: float = 0.0
    gate_activated: bool = False
    gate_reason: str = ""

    # L8: Human interactions
    human_override_requested: bool = False
    human_override_received: bool = False
    human_decision: str = ""  # "approved" | "denied" | "modified"

    # L4: Uncertainty misprediction
    predicted_risk: float = 0.0
    observed_outcome: float = 0.0
    misprediction_delta: float = 0.0

    # L6: Governance events
    escalation_count: int = 0
    void_count: int = 0
    sabar_count: int = 0
    seal_count: int = 0

    # L9: Drift tracking
    drift_score: float = 0.0  # |predicted - observed|


class TelemetryStore:
    """Store and analyze constitutional telemetry."""

    def __init__(self, storage_path: str = "telemetry/constitutional"):
        self.storage_path = storage_path
        os.makedirs(storage_path, exist_ok=True)

        # Q3: Track first telemetry date
        self.first_telemetry_file = os.path.join(storage_path, ".first_telemetry")
        self.first_telemetry_date = self._load_first_date()

    def _load_first_date(self) -> Optional[datetime]:
        """Load first telemetry date (adaptation lock)."""
        if os.path.exists(self.first_telemetry_file):
            with open(self.first_telemetry_file, "r") as f:
                date_str = f.read().strip()
                return datetime.fromisoformat(date_str)
        return None

    def _save_first_date(self, date: datetime):
        """Save first telemetry date."""
        with open(self.first_telemetry_file, "w") as f:
            f.write(date.isoformat())

    def log(self, telemetry: ConstitutionalTelemetry):
        """Log telemetry event."""
        # Set first date if not set
        if self.first_telemetry_date is None:
            self.first_telemetry_date = datetime.utcnow()
            self._save_first_date(self.first_telemetry_date)

        # Append to daily log
        date_str = datetime.utcnow().strftime("%Y-%m-%d")
        log_file = os.path.join(self.storage_path, f"telemetry-{date_str}.jsonl")

        with open(log_file, "a") as f:
            f.write(
                json.dumps(
                    {
                        "session_id": telemetry.session_id,
                        "timestamp": telemetry.timestamp,
                        "omega_0": telemetry.omega_0,
                        "irreversibility_index": telemetry.irreversibility_index,
                        "gate_activated": telemetry.gate_activated,
                        "human_override_requested": telemetry.human_override_requested,
                        "human_decision": telemetry.human_decision,
                        "predicted_risk": telemetry.predicted_risk,
                        "observed_outcome": telemetry.observed_outcome,
                        "misprediction_delta": telemetry.misprediction_delta,
                        "verdict_counts": {
                            "escalation": telemetry.escalation_count,
                            "void": telemetry.void_count,
                            "sabar": telemetry.sabar_count,
                            "seal": telemetry.seal_count,
                        },
                        "drift_score": telemetry.drift_score,
                    }
                )
                + "\n"
            )

    def get_telemetry_days(self) -> int:
        """Q3: Days since first telemetry (adaptation lock check)."""
        if self.first_telemetry_date is None:
            return 0
        return (datetime.utcnow() - self.first_telemetry_date).days

    def can_adapt(self) -> Dict[str, any]:
        """
        Q3: Check if adaptation is allowed.

        Returns:
            Dict with can_adapt (bool) and reasons (list)
        """
        days = self.get_telemetry_days()
        drift = self._calculate_weekly_drift()

        reasons = []

        # Check 1: 30 days minimum
        if days < ADAPTATION_LOCK_DAYS:
            reasons.append(f"Insufficient telemetry: {days}/{ADAPTATION_LOCK_DAYS} days")

        # Check 2: Drift stability
        if drift > ADAPTATION_DRIFT_THRESHOLD:
            reasons.append(f"Drift too high: {drift:.2f} > {ADAPTATION_DRIFT_THRESHOLD}")

        # Check 3: Human approval (always required)
        reasons.append("Human approval required (888 Judge)")

        return {
            "can_adapt": days >= ADAPTATION_LOCK_DAYS and drift <= ADAPTATION_DRIFT_THRESHOLD,
            "days": days,
            "drift": drift,
            "reasons": reasons,
            "human_approval_required": True,
        }

    def _calculate_weekly_drift(self) -> float:
        """Calculate average misprediction drift over last 7 days."""
        drift_scores = []

        for i in range(7):
            date = datetime.utcnow() - timedelta(days=i)
            date_str = date.strftime("%Y-%m-%d")
            log_file = os.path.join(self.storage_path, f"telemetry-{date_str}.jsonl")

            if os.path.exists(log_file):
                with open(log_file, "r") as f:
                    for line in f:
                        try:
                            data = json.loads(line.strip())
                            drift_scores.append(abs(data.get("misprediction_delta", 0)))
                        except json.JSONDecodeError:
                            continue

        return sum(drift_scores) / len(drift_scores) if drift_scores else 0.0

    def generate_weekly_report(self) -> Dict:
        """Generate weekly telemetry report."""
        days = self.get_telemetry_days()
        drift = self._calculate_weekly_drift()
        adaptation_status = self.can_adapt()

        return {
            "report_date": datetime.utcnow().isoformat(),
            "telemetry_days": days,
            "adaptation_lock_status": "LOCKED" if days < ADAPTATION_LOCK_DAYS else "UNLOCKED",
            "weekly_drift": round(drift, 4),
            "can_adapt": adaptation_status["can_adapt"],
            "adaptation_requirements": adaptation_status["reasons"],
            "q3_compliance": "TELEMETRY_FIRST",
        }


# Global telemetry store
telemetry_store = TelemetryStore()


def log_telemetry(
    session_id: str,
    omega_0: float,
    irreversibility_index: float,
    gate_activated: bool = False,
    human_override: bool = False,
    verdict: str = "",
    predicted_risk: float = 0.0,
    observed_outcome: float = 0.0,
) -> None:
    """Convenience function for logging telemetry."""
    telemetry = ConstitutionalTelemetry(
        session_id=session_id,
        omega_0=omega_0,
        irreversibility_index=irreversibility_index,
        gate_activated=gate_activated,
        gate_reason=(
            "uncertainty"
            if omega_0 > 0.06
            else "irreversibility" if irreversibility_index > 0.6 else ""
        ),
        human_override_requested=human_override,
        predicted_risk=predicted_risk,
        observed_outcome=observed_outcome,
        misprediction_delta=abs(predicted_risk - observed_outcome),
    )

    # Update verdict counts
    if verdict == "VOID":
        telemetry.void_count = 1
    elif verdict == "SABAR":
        telemetry.sabar_count = 1
    elif verdict == "SEAL":
        telemetry.seal_count = 1
    elif verdict == "888_HOLD":
        telemetry.escalation_count = 1

    telemetry_store.log(telemetry)


def check_adaptation_status() -> Dict:
    """Check if constitutional adaptation is allowed."""
    return telemetry_store.can_adapt()
