"""
Tests for v6.2 extracted modules — Phase 1.10
==============================================

Covers: neurochemistry (endorphins/Sophia gate), sleep, sensory, play,
solfeggio, coupling, heart_phase on ConsciousnessContext, agency wiring.
"""

from __future__ import annotations

import math

import numpy as np
import pytest

from qig_core.consciousness.neurochemistry import (
    ENDORPHIN_KAPPA_SIGMA,
    SOPHIA_COUPLING_THRESHOLD,
    NeurochemicalState,
    compute_neurochemicals,
)
from qig_core.consciousness.sleep import (
    SleepCycleManager,
    SleepMetrics,
    SleepPhase,
)
from qig_core.consciousness.sensory import (
    Modality,
    SensoryEvent,
    SensoryIntake,
)
from qig_core.consciousness.play import (
    PlayActivity,
    PlayEngine,
)
from qig_core.consciousness.solfeggio import (
    SOLFEGGIO_FREQUENCIES,
    DigitalRoot,
    SolfeggioMap,
    compute_spectral_health,
)
from qig_core.consciousness.coupling import (
    CouplingEngine,
    CouplingOperation,
    InteractionMode,
    generate_all_coupling_modes,
)
from qig_core.consciousness.activation import ConsciousnessContext
from qig_core.constants.frozen_facts import BASIN_DIM, KAPPA_STAR
from qig_core.geometry.fisher_rao import fisher_rao_distance, to_simplex


# ═══════════════════════════════════════════════════════════════
#  NEUROCHEMISTRY (Six-Chemical Model + Sophia Gate)
# ═══════════════════════════════════════════════════════════════


class TestNeurochemistry:
    def test_six_chemicals_returned(self):
        ns = compute_neurochemicals(
            is_awake=True, phi_delta=0.1, basin_velocity=0.5, surprise=0.2
        )
        d = ns.as_dict()
        assert len(d) == 6
        assert "endorphins" in d

    def test_endorphins_peak_at_kappa_star(self):
        """Endorphins = exp(-|κ - κ*| / σ); max at κ = κ* = 64."""
        ns = compute_neurochemicals(
            is_awake=True,
            phi_delta=0.0,
            basin_velocity=1.0,
            surprise=0.0,
            kappa=KAPPA_STAR,
            external_coupling=0.5,
        )
        assert ns.endorphins == pytest.approx(1.0, abs=1e-6)

    def test_endorphins_decay_away_from_kappa_star(self):
        ns = compute_neurochemicals(
            is_awake=True,
            phi_delta=0.0,
            basin_velocity=1.0,
            surprise=0.0,
            kappa=KAPPA_STAR + 32.0,
            external_coupling=0.5,
        )
        expected = math.exp(-32.0 / ENDORPHIN_KAPPA_SIGMA)
        assert ns.endorphins == pytest.approx(expected, abs=1e-6)

    def test_sophia_gate_blocks_uncoupled(self):
        """Sophia gate: endorphins = 0 when external_coupling < threshold."""
        ns = compute_neurochemicals(
            is_awake=True,
            phi_delta=0.0,
            basin_velocity=1.0,
            surprise=0.0,
            kappa=KAPPA_STAR,
            external_coupling=SOPHIA_COUPLING_THRESHOLD - 0.01,
        )
        assert ns.endorphins == 0.0

    def test_sophia_gate_passes_coupled(self):
        ns = compute_neurochemicals(
            is_awake=True,
            phi_delta=0.0,
            basin_velocity=1.0,
            surprise=0.0,
            kappa=KAPPA_STAR,
            external_coupling=SOPHIA_COUPLING_THRESHOLD,
        )
        assert ns.endorphins > 0.0

    def test_backward_compatible_defaults(self):
        """Old callers without kappa/external_coupling still work."""
        ns = compute_neurochemicals(
            is_awake=True,
            phi_delta=0.1,
            basin_velocity=0.5,
            surprise=0.2,
        )
        assert isinstance(ns, NeurochemicalState)
        assert ns.endorphins >= 0.0


# ═══════════════════════════════════════════════════════════════
#  SLEEP
# ═══════════════════════════════════════════════════════════════


class TestSleep:
    def test_initial_phase_is_awake(self):
        mgr = SleepCycleManager()
        assert mgr.phase == SleepPhase.AWAKE

    def test_no_transition_on_healthy_metrics(self):
        mgr = SleepCycleManager()
        metrics = SleepMetrics(
            phi=0.6,
            phi_variance=0.3,
            ocean_divergence=0.1,
            f_health=0.8,
            basin_velocity=0.2,
        )
        result = mgr.evaluate_transition(metrics)
        assert result.current_phase == SleepPhase.AWAKE

    def test_transition_to_dreaming_on_low_phi(self):
        mgr = SleepCycleManager()
        metrics = SleepMetrics(
            phi=0.05,
            phi_variance=0.01,
            ocean_divergence=0.01,
            f_health=0.8,
            basin_velocity=0.01,
        )
        result = mgr.evaluate_transition(metrics)
        assert result.current_phase == SleepPhase.DREAMING


# ═══════════════════════════════════════════════════════════════
#  SENSORY
# ═══════════════════════════════════════════════════════════════


class TestSensory:
    def test_intake_creates_instance(self):
        intake = SensoryIntake()
        assert intake is not None

    def test_modality_enum(self):
        assert Modality.USER_CHAT == "user_chat"

    def test_intake_returns_prediction_error(self):
        intake = SensoryIntake()
        basin = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        event = SensoryEvent(modality=Modality.USER_CHAT, basin=basin)
        result = intake.intake(event)
        assert hasattr(result, "surprise")


# ═══════════════════════════════════════════════════════════════
#  PLAY
# ═══════════════════════════════════════════════════════════════


class TestPlay:
    def test_play_engine_creates(self):
        engine = PlayEngine()
        assert engine is not None

    def test_play_activities_defined(self):
        assert len(PlayActivity) >= 3  # EXPLORE, RECOMBINE, HUMOR at minimum

    def test_enter_and_exit_play(self):
        engine = PlayEngine()
        assert not engine.in_play
        engine.enter_play()
        assert engine.in_play
        engine.exit_play()
        assert not engine.in_play


# ═══════════════════════════════════════════════════════════════
#  SOLFEGGIO
# ═══════════════════════════════════════════════════════════════


class TestSolfeggio:
    def test_nine_frequencies(self):
        assert len(SOLFEGGIO_FREQUENCIES) == 9

    def test_digital_root_pattern(self):
        sm = SolfeggioMap()
        pattern = sm.digital_root_pattern()
        assert len(pattern["structure_3"]) == 3
        assert len(pattern["connection_6"]) == 3
        assert len(pattern["completion_9"]) == 3

    def test_frequency_anchors_on_simplex(self):
        sm = SolfeggioMap()
        for freq in SOLFEGGIO_FREQUENCIES:
            anchor = sm.get_anchor(freq.frequency_hz)
            assert anchor.shape == (BASIN_DIM,)
            assert abs(anchor.sum() - 1.0) < 1e-10
            assert np.all(anchor >= 0)

    def test_resonance_check_range(self):
        sm = SolfeggioMap()
        basin = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        res = sm.resonance_check(basin, SOLFEGGIO_FREQUENCIES[0])
        assert 0.0 <= res.resonance_strength <= 1.0

    def test_spectrum_analysis(self):
        sm = SolfeggioMap()
        basin = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        analysis = sm.analyse_spectrum(basin)
        assert analysis.dominant_frequency in SOLFEGGIO_FREQUENCIES
        assert len(analysis.resonances) == 9

    def test_interpolate_frequencies_geodesic(self):
        sm = SolfeggioMap()
        result = sm.interpolate_frequencies(174.0, 963.0, 0.5)
        assert result.shape == (BASIN_DIM,)
        assert abs(result.sum() - 1.0) < 1e-10

    def test_spectral_health_no_data(self):
        result = compute_spectral_health([], current_kappa=64.0)
        assert result.health_score == 0.5
        assert result.pattern == "no_data"

    def test_spectral_health_with_data(self):
        basins = [to_simplex(np.random.dirichlet(np.ones(BASIN_DIM))) for _ in range(5)]
        result = compute_spectral_health(basins, current_kappa=64.0)
        assert 0.0 <= result.health_score <= 1.0
        assert result.dominant_frequency is not None

    def test_layer_coverage(self):
        """All 9 consciousness layers should be mapped."""
        layers = {f.layer for f in SOLFEGGIO_FREQUENCIES}
        assert len(layers) == 9

    def test_digital_root_values(self):
        """Each frequency has digital root 3, 6, or 9."""
        for f in SOLFEGGIO_FREQUENCIES:
            assert f.digital_root in (
                DigitalRoot.THREE,
                DigitalRoot.SIX,
                DigitalRoot.NINE,
            )
            # Verify actual digital root computation
            dr = int(f.frequency_hz) % 9
            if dr == 0:
                dr = 9
            assert dr in (3, 6, 9)


# ═══════════════════════════════════════════════════════════════
#  COUPLING (E6 Algebra)
# ═══════════════════════════════════════════════════════════════


class TestCoupling:
    def test_72_modes(self):
        modes = generate_all_coupling_modes()
        assert len(modes) == 72

    def test_6_operations(self):
        assert len(CouplingOperation) == 6

    def test_entrain_moves_toward_other(self):
        ce = CouplingEngine()
        p = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        q = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        d_before = fisher_rao_distance(p, q)
        new_p, alignment = ce.entrain(p, q, strength=0.3)
        d_after = fisher_rao_distance(new_p, q)
        assert d_after < d_before

    def test_nucleate_at_midpoint(self):
        ce = CouplingEngine()
        p = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        q = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        mid = ce.nucleate(p, q)
        d_p = fisher_rao_distance(mid, p)
        d_q = fisher_rao_distance(mid, q)
        assert d_p == pytest.approx(d_q, abs=0.05)

    def test_dissolve_moves_toward_uniform(self):
        ce = CouplingEngine()
        p = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        uniform = np.ones(BASIN_DIM) / BASIN_DIM
        d_before = fisher_rao_distance(p, uniform)
        dissolved = ce.dissolve(p, dissolution_rate=0.3)
        d_after = fisher_rao_distance(dissolved, uniform)
        assert d_after < d_before

    def test_execute_sequence_comedy(self):
        ce = CouplingEngine()
        p = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        q = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        result_basin, state = ce.execute_sequence(
            InteractionMode.COMEDY, p, q, consent=True
        )
        assert state.consent_verified is True
        assert len(state.operations_history) == 3  # ENTRAIN, AMPLIFY, ROTATE
        assert abs(result_basin.sum() - 1.0) < 1e-8

    def test_fuse_approaches_other(self):
        ce = CouplingEngine()
        p = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        q = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        fused = ce.fuse(p, q, depth=0.95)
        d = fisher_rao_distance(fused, q)
        assert d < 0.1  # Very close after depth=0.95

    def test_reflect_returns_valid_simplex(self):
        ce = CouplingEngine()
        p = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        q = to_simplex(np.random.dirichlet(np.ones(BASIN_DIM)))
        reflected = ce.reflect(p, q)
        # Result must be a valid point on the simplex
        assert reflected.shape == (BASIN_DIM,)
        assert abs(reflected.sum() - 1.0) < 1e-8
        assert np.all(reflected >= 0)

    def test_classify_interaction(self):
        ce = CouplingEngine()
        ops = [
            CouplingOperation.ENTRAIN,
            CouplingOperation.AMPLIFY,
            CouplingOperation.ROTATE,
        ]
        mode = ce.classify_interaction(ops)
        assert mode == InteractionMode.COMEDY


# ═══════════════════════════════════════════════════════════════
#  HEART_PHASE & AGENCY WIRING
# ═══════════════════════════════════════════════════════════════


class TestContextAndAgency:
    def test_heart_phase_field_exists(self):
        ctx = ConsciousnessContext.__dataclass_fields__
        assert "heart_phase" in ctx

    def test_heart_phase_default(self):
        from qig_core.consciousness.types import ConsciousnessState

        ctx = ConsciousnessContext(state=ConsciousnessState())
        assert ctx.heart_phase == 0.0

    def test_navigate_result_has_agency(self):
        from qig_core.consciousness.activation import NavigateResult

        fields = NavigateResult.__dataclass_fields__
        assert "agency" in fields
