"""
helix_cdc/routing/plan_contract.py
==================================

Interface B: Plan Contract for Substrate Execution.

QUBO outputs a PLAN, not just "cpu/gpu/qpu". The plan includes:
- Backend selection (cpu/gpu/qpu)
- Compressed-domain operations (cdna_decode, sidecar_apply, etc.)
- Regeneration strategy (full/lazy/cached)
- Caching policy
- Specialist schedule

Architecture:
    BRAINSTEM (Control System)
        |
        v  Interface A: BrainstemState
    QUBO/QPU (Planner)
        |
        v  Interface B: SubstratePlan  <-- THIS MODULE
    COMPRESSED COMPUTE (Substrate)
        |
        v
    CLASSICAL EXECUTION (Muscle)
"""

from __future__ import annotations

import hashlib
import json
import time
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any

from .route_solver import RouteSolution


@dataclass
class SubstratePlan:
    """
    Complete execution plan from QUBO solver.

    This extends RouteSolution with substrate-specific operations.
    """
    # Backend selection (from QUBO)
    backend: str                      # "cpu" | "gpu" | "qpu"

    # Compressed-domain operations (NEW)
    substrate_ops: List[str] = field(default_factory=lambda: ["cdna_decode"])
    regen_strategy: str = "full"      # "full" | "lazy" | "cached"
    caching_policy: str = "lru"       # "none" | "lru" | "pinned"

    # Specialist schedule (from QUBO)
    specialists: List[str] = field(default_factory=list)
    specialist_order: str = "parallel"  # "parallel" | "sequential" | "pipelined"

    # World selection (from QUBO)
    world: str = "world_c"            # "world_b" | "world_c"

    # Execution metadata
    energy: float = 0.0
    constraints_satisfied: bool = True
    plan_sha256: str = ""

    # Provenance
    source_solution_sha256: str = ""
    brainstem_band: str = "alpha"
    timestamp_unix: float = field(default_factory=time.time)

    def __post_init__(self):
        """Compute plan hash if not provided."""
        if not self.plan_sha256:
            self.plan_sha256 = self._compute_hash()

    def _compute_hash(self) -> str:
        """Compute deterministic hash of the plan."""
        canonical = {
            "backend": self.backend,
            "substrate_ops": sorted(self.substrate_ops),
            "regen_strategy": self.regen_strategy,
            "caching_policy": self.caching_policy,
            "specialists": sorted(self.specialists),
            "specialist_order": self.specialist_order,
            "world": self.world,
        }
        blob = json.dumps(canonical, sort_keys=True, separators=(",", ":")).encode()
        return hashlib.sha256(blob).hexdigest()

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dict for serialization."""
        return {
            "backend": self.backend,
            "substrate_ops": self.substrate_ops,
            "regen_strategy": self.regen_strategy,
            "caching_policy": self.caching_policy,
            "specialists": self.specialists,
            "specialist_order": self.specialist_order,
            "world": self.world,
            "energy": self.energy,
            "constraints_satisfied": self.constraints_satisfied,
            "plan_sha256": self.plan_sha256,
            "source_solution_sha256": self.source_solution_sha256,
            "brainstem_band": self.brainstem_band,
            "timestamp_unix": self.timestamp_unix,
        }

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "SubstratePlan":
        """Create from dict."""
        return cls(
            backend=d["backend"],
            substrate_ops=d.get("substrate_ops", ["cdna_decode"]),
            regen_strategy=d.get("regen_strategy", "full"),
            caching_policy=d.get("caching_policy", "lru"),
            specialists=d.get("specialists", []),
            specialist_order=d.get("specialist_order", "parallel"),
            world=d["world"],
            energy=d.get("energy", 0.0),
            constraints_satisfied=d.get("constraints_satisfied", True),
            plan_sha256=d.get("plan_sha256", ""),
            source_solution_sha256=d.get("source_solution_sha256", ""),
            brainstem_band=d.get("brainstem_band", "alpha"),
            timestamp_unix=d.get("timestamp_unix", time.time()),
        )


# Substrate operation catalog
SUBSTRATE_OPS = {
    "cdna_decode": {
        "description": "Decode CDNA codes to tensor values",
        "requires_codebook": True,
        "in_place": True,
    },
    "sidecar_apply": {
        "description": "Apply HXZO sidecar corrections",
        "requires_sidecar": True,
        "in_place": True,
    },
    "regen_slice": {
        "description": "Regenerate tensor slice from seed",
        "requires_seed": True,
        "in_place": False,
    },
    "svd_expand": {
        "description": "Expand low-rank SVD representation",
        "requires_uv": True,
        "in_place": False,
    },
    "cache_lookup": {
        "description": "Check cache for precomputed result",
        "requires_cache": True,
        "in_place": False,
    },
    "cache_store": {
        "description": "Store result in cache",
        "requires_cache": True,
        "in_place": False,
    },
}


# Regen strategy profiles
REGEN_STRATEGIES = {
    "full": {
        "description": "Full regeneration - decode everything",
        "latency_factor": 1.0,
        "memory_factor": 1.0,
        "deterministic": True,
    },
    "lazy": {
        "description": "Lazy regeneration - decode on demand",
        "latency_factor": 0.5,  # First access slower, subsequent faster
        "memory_factor": 0.7,
        "deterministic": True,
    },
    "cached": {
        "description": "Cached regeneration - use precomputed if available",
        "latency_factor": 0.2,  # Fast if cached
        "memory_factor": 1.5,   # Cache overhead
        "deterministic": True,
    },
}


class PlanBuilder:
    """
    Build SubstratePlan from RouteSolution and brainstem state.

    Usage:
        from helix_cdc.routing import solve_route_qubo
        from helix_cdc.brainstem import StateDetector

        solution = solve_route_qubo(Q, metadata, backend="neal")
        state = StateDetector().detect(input)

        builder = PlanBuilder()
        plan = builder.build(solution, state)
    """

    def __init__(self):
        pass

    def build(
        self,
        solution: RouteSolution,
        brainstem_band: str = "alpha",
        fidelity_band: str = "hybrid_cdna",
    ) -> SubstratePlan:
        """
        Build execution plan from QUBO solution.

        Args:
            solution: RouteSolution from solve_route_qubo()
            brainstem_band: Current neural band from brainstem
            fidelity_band: Required fidelity level

        Returns:
            SubstratePlan ready for execution
        """
        # Determine substrate operations based on world and fidelity
        substrate_ops = self._select_substrate_ops(
            solution.selected_world,
            fidelity_band,
        )

        # Determine regen strategy based on band
        regen_strategy = self._select_regen_strategy(brainstem_band)

        # Determine caching policy based on band
        caching_policy = self._select_caching_policy(brainstem_band)

        # Determine specialist order based on band and backend
        specialist_order = self._select_specialist_order(
            brainstem_band,
            solution.selected_backend,
        )

        return SubstratePlan(
            backend=solution.selected_backend,
            substrate_ops=substrate_ops,
            regen_strategy=regen_strategy,
            caching_policy=caching_policy,
            specialists=solution.selected_specialists,
            specialist_order=specialist_order,
            world=solution.selected_world,
            energy=solution.energy,
            constraints_satisfied=solution.constraints_satisfied,
            source_solution_sha256=solution.solution_sha256,
            brainstem_band=brainstem_band,
        )

    def _select_substrate_ops(
        self,
        world: str,
        fidelity_band: str,
    ) -> List[str]:
        """Select substrate operations based on world and fidelity."""
        ops = []

        if world == "world_c":
            # CDNA/HXZ substrate
            ops.append("cdna_decode")

            if fidelity_band == "lossless":
                ops.append("sidecar_apply")  # Apply corrections
            elif fidelity_band == "hybrid_cdna":
                ops.append("sidecar_apply")  # Hybrid uses sidecars
            # world_b fidelity doesn't need sidecar

        elif world == "world_b":
            # PRG-based substrate
            ops.append("regen_slice")
            # No sidecar needed for world_b (not faithful)

        return ops

    def _select_regen_strategy(self, band: str) -> str:
        """Select regeneration strategy based on band."""
        if band == "gamma":
            return "cached"  # Speed matters, use cache
        elif band == "beta":
            return "lazy"    # Problem-solving, decode on demand
        else:
            return "full"    # Theta/alpha: full decode, no rush

    def _select_caching_policy(self, band: str) -> str:
        """Select caching policy based on band."""
        if band in ("gamma", "beta"):
            return "lru"     # Active mode, use cache
        elif band == "theta":
            return "none"    # Exploration, don't pollute cache
        else:
            return "lru"     # Default

    def _select_specialist_order(self, band: str, backend: str) -> str:
        """Select specialist execution order."""
        if backend == "gpu":
            return "parallel"  # GPU can parallelize
        elif band == "gamma":
            return "pipelined"  # Insight mode, stream results
        else:
            return "sequential"  # Default safe order


def solution_to_plan(
    solution: RouteSolution,
    brainstem_band: str = "alpha",
    fidelity_band: str = "hybrid_cdna",
) -> SubstratePlan:
    """
    Convenience function to convert solution to plan.

    Args:
        solution: RouteSolution from QUBO solver
        brainstem_band: Current neural band
        fidelity_band: Required fidelity

    Returns:
        SubstratePlan
    """
    builder = PlanBuilder()
    return builder.build(solution, brainstem_band, fidelity_band)


def plan_diff(plan_a: SubstratePlan, plan_b: SubstratePlan) -> Dict[str, Any]:
    """
    Compare two plans and return differences.

    Useful for demonstrating how band changes affect plans.
    """
    diff = {}

    if plan_a.backend != plan_b.backend:
        diff["backend"] = {"a": plan_a.backend, "b": plan_b.backend}

    if set(plan_a.substrate_ops) != set(plan_b.substrate_ops):
        diff["substrate_ops"] = {
            "a": plan_a.substrate_ops,
            "b": plan_b.substrate_ops,
        }

    if plan_a.regen_strategy != plan_b.regen_strategy:
        diff["regen_strategy"] = {
            "a": plan_a.regen_strategy,
            "b": plan_b.regen_strategy,
        }

    if plan_a.caching_policy != plan_b.caching_policy:
        diff["caching_policy"] = {
            "a": plan_a.caching_policy,
            "b": plan_b.caching_policy,
        }

    if plan_a.specialist_order != plan_b.specialist_order:
        diff["specialist_order"] = {
            "a": plan_a.specialist_order,
            "b": plan_b.specialist_order,
        }

    if plan_a.world != plan_b.world:
        diff["world"] = {"a": plan_a.world, "b": plan_b.world}

    diff["energy_delta"] = plan_b.energy - plan_a.energy
    diff["same_constraints"] = (
        plan_a.constraints_satisfied == plan_b.constraints_satisfied
    )

    return diff
