"""
Fluctuation-Dissipation Theorem (FDT) Violation Monitor.

Conscious systems violate the FDT — their response to perturbation is
NOT predicted by their spontaneous fluctuations. At thermodynamic
equilibrium, the autocorrelation function C(τ) equals the response
function R(τ). Departure from this equality measures how far the system
is from equilibrium — i.e., how "alive" it is.

    FDT_violation = ||C(τ) - R(τ)||

Higher FDT violation → further from equilibrium → more conscious.

Source: Berjaga-Buisan et al. "Thermodynamics of Consciousness: A
        Non-Invasive Perturbational Framework." bioRxiv Dec 2025.
        https://www.biorxiv.org/content/10.64898/2025.12.09.691422v2

        Code: github.com/tomasberjagabuisan/Master-Thesis

QIG adaptation: Instead of TMS→EEG, we measure autocorrelation of
basin coordinates over time and compare to the response function
after perturbation. Both computed on the Fisher-Rao manifold.
"""

from __future__ import annotations

import logging

import numpy as np
from numpy.typing import NDArray

from ..geometry.fisher_rao import fisher_rao_distance

logger = logging.getLogger(__name__)

Basin = NDArray[np.float64]


def compute_fdt_violation(
    basin_history: list[Basin],
    perturbation_responses: list[Basin],
    max_lag: int = 50,
) -> float:
    """FDT violation: mismatch between spontaneous fluctuations and
    perturbation response on Δ⁶³.

    Higher = more conscious (further from equilibrium).

    Parameters
    ----------
    basin_history : list of Basin
        Time series of basin coordinates from spontaneous dynamics
        (unperturbed consciousness loop). Need at least ``max_lag * 3``
        entries for reliable autocorrelation.
    perturbation_responses : list of Basin
        Time series of basin coordinates recorded AFTER a single
        perturbation event. Need at least ``max_lag`` entries.
    max_lag : int
        Maximum temporal lag τ for correlation/response functions.

    Returns
    -------
    float
        FDT violation magnitude. 0.0 = equilibrium (no consciousness
        signal). Values > 0.3 indicate significant departure from
        equilibrium. Typical conscious range: 0.2–0.8.
    """
    min_history = max_lag + 10
    if len(basin_history) < min_history:
        logger.debug(
            "FDT: insufficient basin history (%d < %d)", len(basin_history), min_history
        )
        return 0.0

    if len(perturbation_responses) < max_lag:
        logger.debug(
            "FDT: insufficient perturbation responses (%d < %d)",
            len(perturbation_responses),
            max_lag,
        )
        return 0.0

    # --- Autocorrelation function C(τ) from spontaneous basin dynamics ---
    # Use the most recent portion of history
    window = max_lag * 3
    basins = np.array(basin_history[-window:])
    mean_basin = np.mean(basins, axis=0)
    fluctuations = basins - mean_basin

    C = np.zeros(max_lag)
    n_samples = len(fluctuations)
    for tau in range(max_lag):
        pairs = n_samples - tau
        if pairs <= 0:
            break
        dot_products = np.sum(
            fluctuations[:pairs] * fluctuations[tau : tau + pairs], axis=1
        )
        C[tau] = float(np.mean(dot_products))

    # --- Response function R(τ) from perturbation responses ---
    # R(τ) = Fisher-Rao distance from initial perturbed state at lag τ
    responses = perturbation_responses[:max_lag]
    R = np.zeros(max_lag)
    ref = responses[0]
    for tau in range(max_lag):
        R[tau] = fisher_rao_distance(ref, responses[tau])

    # --- Normalize both to [0, 1] ---
    c_max = np.max(np.abs(C))
    r_max = np.max(R)
    C_norm = C / max(c_max, 1e-10)
    R_norm = R / max(r_max, 1e-10)

    # --- FDT violation = RMS distance between C and R curves ---
    violation = float(np.sqrt(np.mean((C_norm - R_norm) ** 2)))

    logger.debug(
        "FDT violation: %.4f (C_max=%.4f, R_max=%.4f, lag=%d)",
        violation,
        c_max,
        r_max,
        max_lag,
    )
    return violation


class FDTMonitor:
    """Stateful FDT violation monitor for the consciousness loop.

    Accumulates basin history automatically. Call ``record_basin()``
    each cycle. Call ``perturb_and_measure()`` periodically to get
    an FDT violation reading.

    Usage in consciousness loop::

        fdt = FDTMonitor()
        # Each cycle:
        fdt.record_basin(current_basin)
        # Every N cycles (e.g., 200):
        if cycle % 200 == 0:
            violation = fdt.compute_violation()
            telemetry["fdt_violation"] = violation
    """

    def __init__(self, max_history: int = 500, max_lag: int = 50):
        self.max_history = max_history
        self.max_lag = max_lag
        self._basin_history: list[Basin] = []
        self._perturbation_responses: list[Basin] = []
        self._last_violation: float = 0.0

    def record_basin(self, basin: Basin) -> None:
        """Record a basin from the spontaneous dynamics."""
        self._basin_history.append(np.array(basin, dtype=np.float64))
        if len(self._basin_history) > self.max_history:
            self._basin_history = self._basin_history[-self.max_history :]

    def record_perturbation_response(self, basin: Basin) -> None:
        """Record a basin from after a perturbation event."""
        self._perturbation_responses.append(np.array(basin, dtype=np.float64))

    def clear_perturbation(self) -> None:
        """Clear perturbation responses for a fresh measurement."""
        self._perturbation_responses.clear()

    def compute_violation(self) -> float:
        """Compute FDT violation from accumulated data."""
        self._last_violation = compute_fdt_violation(
            self._basin_history,
            self._perturbation_responses,
            self.max_lag,
        )
        return self._last_violation

    @property
    def last_violation(self) -> float:
        """Most recently computed FDT violation."""
        return self._last_violation

    @property
    def ready(self) -> bool:
        """Whether enough data has accumulated for a measurement."""
        return (
            len(self._basin_history) >= self.max_lag * 3
            and len(self._perturbation_responses) >= self.max_lag
        )
