"""
Fractal Correction Engine -- main orchestrator.

A numerical correction framework that uses Frenet-Serret curvature
feedback to detect and mitigate decoherence in open quantum systems.

Pipeline:
  1. Lindblad evolution (with decoherence)
  2. Frenet-Serret frame computation (curvature, torsion)
  3. Trajectory prediction (forward / backward)
  4. Continuous QEC (curvature-feedback correction)
  5. Hausdorff dimension health monitoring
  6. Conservation law monitoring (trace, positivity)

All metrics are COMPUTED from the quantum state, never hardcoded.
The framework should be validated via ablation studies (ablation.py),
convergence testing (convergence.py), and uncertainty quantification
(uncertainty.py) before drawing scientific conclusions.
"""

import logging
import numpy as np
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any

from .config import PHYS, SimulationConfig
from .fidelity import uhlmann_fidelity, validate_density_matrix, infidelity
from .lindblad import (
    LindbladEvolver, build_lindblad_operators,
    compute_decoherence_rates, unitary_evolve, normalize_density_matrix,
)
from .frenet_serret import QuantumFrenetSerret, FrenetSerretFrame
from .trajectory_predictor import TrajectoryPredictor
from .hausdorff import box_counting_dimension, correlation_dimension, assess_health
from .qec_continuous import ContinuousQEC, ContinuousQECResult
from .system_health import SystemHealthMonitor, ThermalState, get_default_monitor

logger = logging.getLogger(__name__)


@dataclass
class EvolutionResult:
    """Complete result of an FCE evolution run."""
    # State history
    density_matrices: List[np.ndarray]
    ideal_states: List[np.ndarray]
    times: np.ndarray

    # Fidelity
    fidelities: np.ndarray          # F(rho_actual, rho_ideal)
    fidelities_uncorrected: np.ndarray  # F without QEC for comparison

    # Frenet-Serret
    curvatures: np.ndarray
    torsions: np.ndarray
    frames: List[Optional[FrenetSerretFrame]]

    # QEC
    kappa_errors: np.ndarray
    tau_errors: np.ndarray
    feedback_norms: np.ndarray
    n_corrections: int

    # Health
    hausdorff_dimension: float
    trajectory_health: str

    # Prediction
    prediction_fidelities: np.ndarray  # How well we predicted each step

    # Conservation monitoring (computed every timestep)
    trace_errors: np.ndarray = None           # |Tr(rho) - 1| per step
    positivity_violations: np.ndarray = None  # min eigenvalue per step

    # Optional per-dimension curvature (populated by navigation layer)
    dimension_curvature_tensors: Optional[List] = None


@dataclass
class EngineConfig:
    """Configuration for the FractalCorrectionEngine."""
    # QEC
    feedback_gain: float = 0.1
    correction_threshold: float = 0.001
    qec_enabled: bool = True

    # Prediction
    prediction_steps: int = 5
    prediction_ds: float = 0.01

    # Health monitoring
    health_window: int = 50  # Steps of trajectory to use for d_H

    # System health monitoring
    enable_thermal_monitoring: bool = True
    health_check_interval: int = 25  # Check system health every N steps


class FractalCorrectionEngine:
    """
    Main FCE orchestrator.

    Combines Lindblad evolution, Frenet-Serret geometry, trajectory
    prediction, and continuous QEC into a single pipeline.

    All metrics are computed from the actual quantum state evolution.
    """

    def __init__(
        self,
        hamiltonian: np.ndarray,
        lindblad_ops: Optional[List[np.ndarray]] = None,
        decoherence_rates: Optional[List[float]] = None,
        system_params: Optional[Dict[str, float]] = None,
        config: Optional[EngineConfig] = None,
    ):
        """
        Args:
            hamiltonian: System Hamiltonian (dim x dim)
            lindblad_ops: Lindblad operators. If None, built from dimension.
            decoherence_rates: Rates for each Lindblad operator. If None,
                computed from system_params.
            system_params: Dict with 'T1', 'T2', 'gate_fidelity' for rate
                computation.  Ignored if decoherence_rates is provided.
            config: Engine configuration. Uses defaults if None.
        """
        self.H = hamiltonian
        self.dim = hamiltonian.shape[0]
        self.config = config or EngineConfig()

        # Build Lindblad operators and rates
        if lindblad_ops is not None:
            self.lindblad_ops = lindblad_ops
        else:
            self.lindblad_ops, _ = build_lindblad_operators(self.dim)

        if decoherence_rates is not None:
            self.rates = decoherence_rates
        elif system_params is not None:
            self.rates = compute_decoherence_rates(self.dim, system_params)
        else:
            # Default: moderate noise
            default_params = {
                'T1': 1e-3, 'T2': 5e-4, 'gate_fidelity': 0.999
            }
            self.rates = compute_decoherence_rates(self.dim, default_params)

        # Sub-modules
        self.evolver = LindbladEvolver(self.H, self.lindblad_ops, self.rates)
        self.fs = QuantumFrenetSerret()
        self.predictor = TrajectoryPredictor()
        self.qec = ContinuousQEC(
            feedback_gain=self.config.feedback_gain,
            correction_threshold=self.config.correction_threshold,
        )

        # System health monitor
        if self.config.enable_thermal_monitoring:
            self.health_monitor = get_default_monitor()
        else:
            self.health_monitor = None

    def evolve(
        self,
        rho_init: np.ndarray,
        dt: float,
        n_steps: int,
    ) -> EvolutionResult:
        """
        Run the full FCE pipeline.

        At each step:
          1. Compute ideal (unitary) state
          2. Evolve actual state under Lindblad decoherence
          3. Also track an uncorrected copy (no QEC) for comparison
          4. Compute Frenet-Serret curvature/torsion
          5. Compute error signal (curvature mismatch)
          6. If error exceeds threshold, apply feedback correction
          7. Record all computed metrics

        Args:
            rho_init: Initial density matrix
            dt: Time step
            n_steps: Number of evolution steps

        Returns:
            EvolutionResult with full metric history
        """
        # Validate input
        checks = validate_density_matrix(rho_init)
        if not checks['valid']:
            raise ValueError(f"Invalid initial density matrix: {checks}")

        # Storage
        rho_actual = rho_init.copy()
        rho_uncorrected = rho_init.copy()

        density_matrices = [rho_actual.copy()]
        ideal_states = [rho_init.copy()]
        times_list = [0.0]

        fids = [1.0]
        fids_uncorrected = [1.0]

        kappas = [self.fs.compute_curvature(rho_actual, self.H)]
        taus = [self.fs.compute_torsion(rho_actual, self.H)]
        kappa_errs = [0.0]
        tau_errs = [0.0]
        fb_norms = [0.0]
        pred_fids = [1.0]
        frames = [None]

        # Conservation monitoring
        trace_errs = [abs(float(np.real(np.trace(rho_actual))) - 1.0)]
        eigvals_init = np.linalg.eigvalsh(rho_actual)
        pos_violations = [float(np.min(eigvals_init))]

        n_corrections = 0

        for step in range(n_steps):
            t = (step + 1) * dt

            # System health gate: check memory AND temperature periodically
            if (
                self.health_monitor is not None
                and step % self.config.health_check_interval == 0
            ):
                health = self.health_monitor.check_memory_pressure()
                if health.thermal_state == ThermalState.HOT:
                    logger.warning(
                        "Step %d/%d: CPU hot -- waiting for cooldown",
                        step, n_steps,
                    )
                    self.health_monitor.wait_until_cool()
                elif health.thermal_state == ThermalState.WARM:
                    self.health_monitor.cooldown_between_steps()

            # 1. Ideal state at this time
            rho_ideal = unitary_evolve(rho_init, self.H, t)
            ideal_states.append(rho_ideal.copy())

            # 2. Evolve actual state under decoherence
            rho_actual = self.evolver.evolve_single_step(rho_actual, dt)

            # Also evolve uncorrected copy
            rho_uncorrected = self.evolver.evolve_single_step(rho_uncorrected, dt)
            fid_uncorrected = uhlmann_fidelity(rho_uncorrected, rho_ideal)

            # 3. Curvature and torsion
            kappa = self.fs.compute_curvature(rho_actual, self.H)
            tau = self.fs.compute_torsion(rho_actual, self.H)

            # 4. Error signal
            kappa_err, tau_err, error_dir = self.qec.compute_error_signal(
                rho_actual, rho_ideal, self.H
            )

            # 5. Apply QEC if enabled and error exceeds threshold
            fb_norm = 0.0
            if self.config.qec_enabled and (
                abs(kappa_err) > self.config.correction_threshold
                or abs(tau_err) > self.config.correction_threshold
            ):
                # Curvature anomaly detected → apply correction
                # Correction strength scales with actual infidelity
                fid_before_correction = uhlmann_fidelity(rho_actual, rho_ideal)
                current_infidelity = 1.0 - fid_before_correction
                alpha = self.config.feedback_gain * current_infidelity
                fb_norm = alpha

                rho_actual = self.qec.steer_correction(
                    rho_actual, rho_ideal, alpha
                )
                n_corrections += 1

            # 6. Compute fidelity (ALWAYS computed, never hardcoded)
            fid = uhlmann_fidelity(rho_actual, rho_ideal)

            # 7. Prediction accuracy (compare current to what we predicted)
            # Use last few frames for prediction
            pred_fid = fid  # Default; overridden when enough history
            if len(density_matrices) >= 3:
                recent_rhos = density_matrices[-3:]
                recent_frames = self.fs.compute_frame(recent_rhos, self.H, dt)
                if recent_frames:
                    pred_result = self.predictor.predict_forward(
                        recent_rhos[-1], recent_frames[-1], dt, 1
                    )
                    if pred_result.predicted_states:
                        pred_fid = uhlmann_fidelity(
                            pred_result.predicted_states[-1], rho_actual
                        )

            # 8. Compute frame
            if len(density_matrices) >= 2:
                tangent = self.fs.compute_tangent(rho_actual, self.H)
                normal = self.fs.compute_normal(rho_actual, self.H, tangent)
                speed = self.fs.evolution_speed(rho_actual, self.H)
                frame = FrenetSerretFrame(
                    time=t,
                    arc_length=speed * t,
                    curvature=kappa,
                    torsion=tau,
                    speed=speed,
                    tangent=tangent,
                    normal=normal,
                )
            else:
                frame = None

            # Record
            density_matrices.append(rho_actual.copy())
            times_list.append(t)
            fids.append(fid)
            fids_uncorrected.append(fid_uncorrected)
            kappas.append(kappa)
            taus.append(tau)
            kappa_errs.append(kappa_err)
            tau_errs.append(tau_err)
            fb_norms.append(fb_norm)
            pred_fids.append(pred_fid)
            frames.append(frame)

            # Conservation monitoring
            trace_errs.append(abs(float(np.real(np.trace(rho_actual))) - 1.0))
            eigvals_step = np.linalg.eigvalsh(rho_actual)
            pos_violations.append(float(np.min(eigvals_step)))

        # Hausdorff dimension from trajectory
        window = min(self.config.health_window, len(density_matrices))
        trajectory_slice = density_matrices[-window:]
        if len(trajectory_slice) >= 10:
            d_H = correlation_dimension(trajectory_slice)
        elif len(trajectory_slice) >= 3:
            d_H = box_counting_dimension(trajectory_slice)
        else:
            d_H = 1.0

        health = assess_health(d_H)

        return EvolutionResult(
            density_matrices=density_matrices,
            ideal_states=ideal_states,
            times=np.array(times_list),
            fidelities=np.array(fids),
            fidelities_uncorrected=np.array(fids_uncorrected),
            curvatures=np.array(kappas),
            torsions=np.array(taus),
            frames=frames,
            kappa_errors=np.array(kappa_errs),
            tau_errors=np.array(tau_errs),
            feedback_norms=np.array(fb_norms),
            n_corrections=n_corrections,
            hausdorff_dimension=d_H,
            trajectory_health=health.value,
            prediction_fidelities=np.array(pred_fids),
            trace_errors=np.array(trace_errs),
            positivity_violations=np.array(pos_violations),
        )

    def summary(self, result: EvolutionResult) -> Dict[str, Any]:
        """
        Produce a human-readable summary of an evolution result.

        All values are COMPUTED from the result, not preset.
        """
        final_fid = float(result.fidelities[-1])
        final_fid_uncorrected = float(result.fidelities_uncorrected[-1])
        error_corrected = 1.0 - final_fid
        error_uncorrected = 1.0 - final_fid_uncorrected

        improvement = (
            error_uncorrected / error_corrected
            if error_corrected > 1e-15
            else float('inf')
        )

        summary = {
            'n_steps': len(result.times) - 1,
            'total_time': float(result.times[-1]),
            'final_fidelity_corrected': final_fid,
            'final_fidelity_uncorrected': final_fid_uncorrected,
            'error_rate_corrected': error_corrected,
            'error_rate_uncorrected': error_uncorrected,
            'error_reduction_factor': improvement,
            'mean_curvature': float(np.mean(result.curvatures)),
            'mean_torsion': float(np.mean(result.torsions)),
            'n_corrections_applied': result.n_corrections,
            'hausdorff_dimension': result.hausdorff_dimension,
            'trajectory_health': result.trajectory_health,
            'mean_prediction_fidelity': float(np.mean(result.prediction_fidelities)),
        }

        # Conservation monitoring
        if result.trace_errors is not None:
            summary['max_trace_error'] = float(np.max(result.trace_errors))
        if result.positivity_violations is not None:
            summary['max_positivity_violation'] = float(
                max(-np.min(result.positivity_violations), 0.0)
            )

        return summary
