#!/usr/bin/env python3
"""
routing_trace_wrapper.py - Routing Trace Wrapper for Se Router

Wraps SeRouter to capture routing decisions for receipt generation.

Usage:
    from helix_cdc.receipts.routing_trace_wrapper import TracingSeRouter

    router = TracingSeRouter(cores=[0, 1, 2, 3])
    core = router.route(se_input=SeInput(H=5.33, C=0.87, D=8))

    # Get routing trace for receipt
    routing = router.get_last_routing()
"""

from __future__ import annotations
import sys
from pathlib import Path
from dataclasses import dataclass
from typing import Dict, List, Any, Optional

# Add runtime to path
sys.path.insert(0, str(Path(__file__).parent.parent))

try:
    from runtime.se_router import SeRouter, SeRouterConfig
except ImportError:
    # Standalone fallback implementation
    SeRouter = None
    SeRouterConfig = None

from .inference_receipt_v1 import Routing, SeInput


@dataclass
class RoutingDecision:
    """Captured routing decision with full context."""
    se_input: SeInput
    se_computed: float
    route_decision: str
    route_reason: str
    core_id: int


class TracingSeRouter:
    """
    Se-based router with tracing for receipt generation.

    Captures routing decisions with full context for audit.
    """

    def __init__(
        self,
        cores: Optional[List[int]] = None,
        window: int = 256,
        epsilon: float = 1e-8,
    ):
        """
        Initialize tracing router.

        Args:
            cores: List of core IDs to route to
            window: Window size for running min/max
            epsilon: Small value to prevent division by zero
        """
        self.cores = cores or [0, 1, 2, 3]
        self.window = window
        self.epsilon = epsilon

        # Internal router (use real one if available)
        if SeRouter is not None:
            config = SeRouterConfig(
                cores=self.cores,
                mode="se_bucket",
                window=window,
                epsilon=epsilon,
            )
            self._router = SeRouter(config)
        else:
            self._router = None

        # Trace history
        self.decisions: List[RoutingDecision] = []
        self.last_decision: Optional[RoutingDecision] = None

    def route(
        self,
        se_input: Optional[SeInput] = None,
        H_entropy: float = 0.0,
        C_coherence: float = 0.0,
        D_depth: float = 0.0,
    ) -> int:
        """
        Route based on Se components and capture trace.

        Args:
            se_input: SeInput dataclass (preferred)
            H_entropy: Shannon entropy (if se_input not provided)
            C_coherence: Contextual coherence (if se_input not provided)
            D_depth: Dimensional depth (if se_input not provided)

        Returns:
            Core ID to route to
        """
        # Build SeInput if not provided
        if se_input is None:
            se_input = SeInput(
                H_entropy=H_entropy,
                C_coherence=C_coherence,
                D_depth=D_depth,
            )

        # Compute Se = H * C * D
        se_computed = se_input.H_entropy * se_input.C_coherence * se_input.D_depth

        # Route (use real router or fallback)
        if self._router is not None:
            core_id = self._router.observe(se_computed)
        else:
            core_id = self._route_fallback(se_computed)

        # Generate reason
        route_reason = self._generate_reason(se_input, se_computed, core_id)

        # Record decision
        decision = RoutingDecision(
            se_input=se_input,
            se_computed=se_computed,
            route_decision=f"core_{core_id}",
            route_reason=route_reason,
            core_id=core_id,
        )
        self.decisions.append(decision)
        self.last_decision = decision

        return core_id

    def _route_fallback(self, se: float) -> int:
        """Fallback routing when real SeRouter not available."""
        # Simple bucketing
        if len(self.cores) == 0:
            return 0

        # Normalize to [0, 1] assuming Se typically in [0, 100]
        se_norm = min(1.0, max(0.0, se / 100.0))
        bucket = int(se_norm * len(self.cores))
        bucket = min(bucket, len(self.cores) - 1)
        return self.cores[bucket]

    def _generate_reason(self, se_input: SeInput, se_computed: float, core_id: int) -> str:
        """Generate human-readable routing reason."""
        if se_computed < 10:
            complexity = "low"
        elif se_computed < 50:
            complexity = "moderate"
        else:
            complexity = "high"

        return (
            f"Se={se_computed:.2f} ({complexity} complexity) "
            f"routes to core_{core_id}. "
            f"Components: H={se_input.H_entropy:.2f}, C={se_input.C_coherence:.2f}, D={se_input.D_depth:.1f}"
        )

    def get_last_routing(self) -> Optional[Routing]:
        """
        Get last routing decision as Routing dataclass.

        Returns:
            Routing dataclass for receipt, or None if no decisions
        """
        if self.last_decision is None:
            return None

        return Routing(
            se_input=self.last_decision.se_input,
            se_computed=self.last_decision.se_computed,
            route_decision=self.last_decision.route_decision,
            route_reason=self.last_decision.route_reason,
        )

    def get_routing_as_dict(self) -> Optional[Dict[str, Any]]:
        """
        Get last routing as dict (for emitter convenience).

        Returns:
            Dict with routing data, or None
        """
        if self.last_decision is None:
            return None

        return {
            "se_input": {
                "H_entropy": self.last_decision.se_input.H_entropy,
                "C_coherence": self.last_decision.se_input.C_coherence,
                "D_depth": self.last_decision.se_input.D_depth,
            },
            "se_computed": self.last_decision.se_computed,
            "route_decision": self.last_decision.route_decision,
            "route_reason": self.last_decision.route_reason,
        }

    def get_stats(self) -> Dict[str, Any]:
        """Get routing statistics."""
        if not self.decisions:
            return {"decisions": 0, "cores_used": []}

        se_values = [d.se_computed for d in self.decisions]
        cores_used = set(d.core_id for d in self.decisions)

        return {
            "decisions": len(self.decisions),
            "cores_used": sorted(cores_used),
            "se_min": min(se_values),
            "se_max": max(se_values),
            "se_mean": sum(se_values) / len(se_values),
        }

    def reset(self) -> None:
        """Reset decision history."""
        self.decisions = []
        self.last_decision = None
        if self._router is not None:
            self._router.reset()


# =============================================================================
# DEMO
# =============================================================================

if __name__ == "__main__":
    print("TracingSeRouter Demo")
    print("=" * 60)

    router = TracingSeRouter(cores=[0, 1, 2, 3])

    # Simulate various Se inputs
    test_cases = [
        SeInput(H_entropy=5.33, C_coherence=0.87, D_depth=8),   # Se ≈ 37
        SeInput(H_entropy=2.0, C_coherence=0.5, D_depth=4),     # Se = 4
        SeInput(H_entropy=7.5, C_coherence=0.95, D_depth=12),   # Se ≈ 85.5
        SeInput(H_entropy=1.0, C_coherence=0.1, D_depth=1),     # Se = 0.1
    ]

    print("\n1. Routing Decisions:")
    for se_input in test_cases:
        core = router.route(se_input=se_input)
        routing = router.get_last_routing()
        print(f"\n   Se Input: H={se_input.H_entropy:.2f}, C={se_input.C_coherence:.2f}, D={se_input.D_depth}")
        print(f"   Se Computed: {routing.se_computed:.2f}")
        print(f"   Route: {routing.route_decision}")
        print(f"   Reason: {routing.route_reason}")

    print("\n2. Routing Stats:")
    stats = router.get_stats()
    for k, v in stats.items():
        print(f"   {k}: {v}")

    print("\n3. Last Routing as Dict:")
    routing_dict = router.get_routing_as_dict()
    if routing_dict:
        for k, v in routing_dict.items():
            print(f"   {k}: {v}")
