#!/usr/bin/env python3
"""
shell_router.py - Φ-Shell Scar Tracking & Reflex Escalation (WO#NET_JAZZ_07)
=============================================================================

The "scar on the shell" pattern:

1. Every request that doesn't land cleanly on the Φ-lattice gets SCARRED
2. Scars accumulate - rotation along the spiral tries to realign
3. After N scars in the same region → ESCALATE to reflex queue
4. Reflex (Red/Blue/Purple) decides: quarantine, reweight, or reschedule

This is a circuit-breaker with Φ-awareness.

Usage:
    from helix_cdc.jazz.shell_router import ShellRouter, ScarRecord

    router = ShellRouter(scar_threshold=3, resonance_floor=0.7)

    # Check before firing
    if router.should_fire(lane_id="chatgpt_api"):
        result = fire_request()
        router.record_result(lane_id, result)
    else:
        # Lane is in reflex mode - escalated
        reflex_queue.push(lane_id, result)
"""

from __future__ import annotations
import time
import statistics
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Dict, List, Any, Optional, Callable
from collections import defaultdict
import json

from helix_cdc.jazz.fibpi3d_timing import measure_resonance, GOLDEN_RATIO
from helix_cdc.jazz.combustion_sync import get_cpu_load


@dataclass
class ScarRecord:
    """A scar on the Φ-shell - record of a misaligned hit."""
    timestamp: float
    lane_id: str
    reason: str  # "resonance_low", "valley_missed", "rtt_spike", "cpu_spike"
    resonance_score: float
    cpu_at_fire: float
    rtt_ms: float
    valley_found: bool
    metadata: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "timestamp": self.timestamp,
            "lane_id": self.lane_id,
            "reason": self.reason,
            "resonance_score": self.resonance_score,
            "cpu_at_fire": self.cpu_at_fire,
            "rtt_ms": self.rtt_ms,
            "valley_found": self.valley_found,
            "metadata": self.metadata,
        }


@dataclass
class LaneStatus:
    """Status of a routing lane."""
    lane_id: str
    scars: List[ScarRecord] = field(default_factory=list)
    total_requests: int = 0
    successful_requests: int = 0
    escalated: bool = False
    escalated_at: Optional[float] = None
    rtt_history: List[float] = field(default_factory=list)

    @property
    def scar_count(self) -> int:
        """Recent scars (last 60 seconds)."""
        cutoff = time.time() - 60.0
        return sum(1 for s in self.scars if s.timestamp > cutoff)

    @property
    def scar_rate(self) -> float:
        """Scars per request."""
        if self.total_requests == 0:
            return 0.0
        return len(self.scars) / self.total_requests

    @property
    def avg_rtt(self) -> float:
        if not self.rtt_history:
            return 0.0
        return statistics.mean(self.rtt_history[-20:])  # Last 20

    @property
    def rtt_trend(self) -> str:
        """Is RTT getting worse?"""
        if len(self.rtt_history) < 5:
            return "unknown"
        recent = self.rtt_history[-5:]
        older = self.rtt_history[-10:-5] if len(self.rtt_history) >= 10 else self.rtt_history[:5]
        if not older:
            return "unknown"
        if statistics.mean(recent) > statistics.mean(older) * 1.5:
            return "degrading"
        elif statistics.mean(recent) < statistics.mean(older) * 0.7:
            return "improving"
        return "stable"

    def to_dict(self) -> Dict[str, Any]:
        return {
            "lane_id": self.lane_id,
            "total_requests": self.total_requests,
            "successful_requests": self.successful_requests,
            "scar_count": self.scar_count,
            "scar_rate": self.scar_rate,
            "escalated": self.escalated,
            "escalated_at": self.escalated_at,
            "avg_rtt": self.avg_rtt,
            "rtt_trend": self.rtt_trend,
        }


class ShellRouter:
    """
    Φ-Shell Router with scar tracking and reflex escalation.

    Tracks misaligned hits (scars) and escalates lanes to reflex
    when they accumulate too many scars.
    """

    def __init__(
        self,
        scar_threshold: int = 3,
        resonance_floor: float = 0.7,
        valley_threshold: float = 40.0,
        rtt_spike_factor: float = 3.0,
        cpu_spike_threshold: float = 80.0,
        cooldown_seconds: float = 30.0,
        on_escalate: Optional[Callable[[str, LaneStatus], None]] = None,
    ):
        """
        Initialize ShellRouter.

        Args:
            scar_threshold: Number of scars before escalating to reflex
            resonance_floor: Minimum resonance score (0-1) before scarring
            valley_threshold: CPU % threshold for valley detection
            rtt_spike_factor: RTT > avg * this factor = spike
            cpu_spike_threshold: CPU % above this = spike
            cooldown_seconds: How long escalated lanes stay in reflex
            on_escalate: Callback when lane escalates
        """
        self.scar_threshold = scar_threshold
        self.resonance_floor = resonance_floor
        self.valley_threshold = valley_threshold
        self.rtt_spike_factor = rtt_spike_factor
        self.cpu_spike_threshold = cpu_spike_threshold
        self.cooldown_seconds = cooldown_seconds
        self.on_escalate = on_escalate

        self._lanes: Dict[str, LaneStatus] = defaultdict(lambda: LaneStatus(lane_id=""))
        self._resonance_window: List[float] = []

    def _get_lane(self, lane_id: str) -> LaneStatus:
        if lane_id not in self._lanes:
            self._lanes[lane_id] = LaneStatus(lane_id=lane_id)
        return self._lanes[lane_id]

    def should_fire(self, lane_id: str) -> bool:
        """
        Check if this lane should fire normally or is escalated to reflex.

        Returns:
            True = fire normally
            False = lane is escalated, send to reflex
        """
        lane = self._get_lane(lane_id)

        # Check if escalation has cooled down
        if lane.escalated and lane.escalated_at:
            if time.time() - lane.escalated_at > self.cooldown_seconds:
                # Cooldown complete, try again
                lane.escalated = False
                lane.escalated_at = None
                lane.scars.clear()  # Reset scars

        return not lane.escalated

    def record_result(
        self,
        lane_id: str,
        rtt_ms: float,
        cpu_at_fire: float,
        valley_found: bool,
        status_code: int = 200,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Optional[ScarRecord]:
        """
        Record a request result and check for scars.

        Returns:
            ScarRecord if this was a scar, None if clean
        """
        lane = self._get_lane(lane_id)
        lane.total_requests += 1
        lane.rtt_history.append(rtt_ms)

        # Trim history
        if len(lane.rtt_history) > 100:
            lane.rtt_history = lane.rtt_history[-100:]

        # Update resonance window
        self._resonance_window.append(rtt_ms)
        if len(self._resonance_window) > 20:
            self._resonance_window = self._resonance_window[-20:]

        # Calculate resonance
        resonance = 0.0
        if len(self._resonance_window) >= 3:
            resonance = measure_resonance(self._resonance_window)

        # Check for scar conditions
        scar_reason = None

        # 1. Resonance too low
        if resonance < self.resonance_floor and len(self._resonance_window) >= 5:
            scar_reason = "resonance_low"

        # 2. Valley missed
        elif not valley_found and cpu_at_fire > self.valley_threshold:
            scar_reason = "valley_missed"

        # 3. RTT spike
        elif lane.avg_rtt > 0 and rtt_ms > lane.avg_rtt * self.rtt_spike_factor:
            scar_reason = "rtt_spike"

        # 4. CPU spike
        elif cpu_at_fire > self.cpu_spike_threshold:
            scar_reason = "cpu_spike"

        # 5. HTTP error
        elif status_code >= 500:
            scar_reason = "http_error"

        if scar_reason:
            # Create scar
            scar = ScarRecord(
                timestamp=time.time(),
                lane_id=lane_id,
                reason=scar_reason,
                resonance_score=resonance,
                cpu_at_fire=cpu_at_fire,
                rtt_ms=rtt_ms,
                valley_found=valley_found,
                metadata=metadata or {},
            )
            lane.scars.append(scar)

            # Check for escalation
            if lane.scar_count >= self.scar_threshold and not lane.escalated:
                lane.escalated = True
                lane.escalated_at = time.time()

                if self.on_escalate:
                    self.on_escalate(lane_id, lane)

            return scar
        else:
            lane.successful_requests += 1
            return None

    def get_lane_status(self, lane_id: str) -> Dict[str, Any]:
        """Get status of a specific lane."""
        return self._get_lane(lane_id).to_dict()

    def get_all_lanes(self) -> Dict[str, Dict[str, Any]]:
        """Get status of all lanes."""
        return {lid: lane.to_dict() for lid, lane in self._lanes.items()}

    def get_escalated_lanes(self) -> List[str]:
        """Get list of currently escalated lane IDs."""
        return [lid for lid, lane in self._lanes.items() if lane.escalated]

    def get_recent_scars(self, limit: int = 20) -> List[Dict[str, Any]]:
        """Get most recent scars across all lanes."""
        all_scars = []
        for lane in self._lanes.values():
            all_scars.extend(lane.scars)

        all_scars.sort(key=lambda s: s.timestamp, reverse=True)
        return [s.to_dict() for s in all_scars[:limit]]

    def force_escalate(self, lane_id: str) -> None:
        """Manually escalate a lane to reflex."""
        lane = self._get_lane(lane_id)
        lane.escalated = True
        lane.escalated_at = time.time()

        if self.on_escalate:
            self.on_escalate(lane_id, lane)

    def force_deescalate(self, lane_id: str) -> None:
        """Manually de-escalate a lane."""
        lane = self._get_lane(lane_id)
        lane.escalated = False
        lane.escalated_at = None
        lane.scars.clear()

    def get_summary(self) -> Dict[str, Any]:
        """Get summary of router state."""
        total_scars = sum(len(lane.scars) for lane in self._lanes.values())
        escalated = self.get_escalated_lanes()

        return {
            "total_lanes": len(self._lanes),
            "total_scars": total_scars,
            "escalated_lanes": len(escalated),
            "escalated_lane_ids": escalated,
            "scar_threshold": self.scar_threshold,
            "resonance_floor": self.resonance_floor,
            "valley_threshold": self.valley_threshold,
            "cooldown_seconds": self.cooldown_seconds,
        }


# Global router instance
_global_router: Optional[ShellRouter] = None


def get_shell_router() -> ShellRouter:
    """Get or create the global shell router."""
    global _global_router
    if _global_router is None:
        _global_router = ShellRouter()
    return _global_router


def check_lane(lane_id: str) -> bool:
    """Check if a lane should fire (not escalated)."""
    return get_shell_router().should_fire(lane_id)


def record_scar(
    lane_id: str,
    rtt_ms: float,
    cpu_at_fire: float,
    valley_found: bool,
    status_code: int = 200,
) -> Optional[ScarRecord]:
    """Record a result and get scar if any."""
    return get_shell_router().record_result(
        lane_id, rtt_ms, cpu_at_fire, valley_found, status_code
    )


# CLI for testing
def _cli():
    import argparse
    import random

    parser = argparse.ArgumentParser(description="Shell Router Test")
    parser.add_argument("--requests", type=int, default=50)
    parser.add_argument("--lanes", type=int, default=3)
    parser.add_argument("--scar-rate", type=float, default=0.3, help="Probability of bad request")
    args = parser.parse_args()

    def on_escalate(lane_id: str, status: LaneStatus):
        print(f"  🚨 ESCALATED: {lane_id} ({status.scar_count} scars)")

    router = ShellRouter(
        scar_threshold=3,
        on_escalate=on_escalate,
    )

    print("=" * 60)
    print("SHELL ROUTER TEST")
    print("=" * 60)
    print(f"Lanes: {args.lanes}")
    print(f"Requests: {args.requests}")
    print(f"Scar probability: {args.scar_rate:.0%}")
    print()

    lane_ids = [f"lane_{i}" for i in range(args.lanes)]

    for i in range(args.requests):
        lane_id = random.choice(lane_ids)

        if not router.should_fire(lane_id):
            print(f"  [{i+1}] {lane_id}: BLOCKED (escalated)")
            continue

        # Simulate request
        is_bad = random.random() < args.scar_rate

        if is_bad:
            rtt = random.uniform(50, 200)  # Spike
            cpu = random.uniform(60, 90)   # High
            valley = False
            status = random.choice([200, 500, 503])
        else:
            rtt = random.uniform(1, 10)    # Normal
            cpu = random.uniform(2, 30)    # Low
            valley = True
            status = 200

        scar = router.record_result(lane_id, rtt, cpu, valley, status)

        if scar:
            print(f"  [{i+1}] {lane_id}: SCAR ({scar.reason})")
        else:
            print(f"  [{i+1}] {lane_id}: OK")

    print()
    print("=" * 60)
    print("SUMMARY")
    print("=" * 60)
    summary = router.get_summary()
    print(f"Total lanes: {summary['total_lanes']}")
    print(f"Total scars: {summary['total_scars']}")
    print(f"Escalated: {summary['escalated_lanes']} ({summary['escalated_lane_ids']})")
    print()

    print("Lane Status:")
    for lane_id in lane_ids:
        status = router.get_lane_status(lane_id)
        print(f"  {lane_id}: {status['total_requests']} req, {status['scar_count']} scars, escalated={status['escalated']}")

    print()
    print("Recent Scars:")
    for scar in router.get_recent_scars(5):
        print(f"  {scar['lane_id']}: {scar['reason']} (res={scar['resonance_score']:.2f}, cpu={scar['cpu_at_fire']:.1f}%)")

    print("=" * 60)


if __name__ == "__main__":
    _cli()
