"""Tests for stability module: gain margin, phase margin from loop gain data."""

import numpy as np

from mcltspice.stability import (
    compute_gain_margin,
    compute_phase_margin,
    compute_stability_metrics,
)


def _second_order_system(freq, wn=1000.0, zeta=0.3):
    """Create a 2nd-order underdamped system: H(s) = wn^2 / (s^2 + 2*zeta*wn*s + wn^2).

    Returns complex loop gain at the given frequencies.
    """
    s = 1j * 2 * np.pi * freq
    return wn**2 / (s**2 + 2 * zeta * wn * s + wn**2)


class TestGainMargin:
    def test_third_order_system(self):
        """A 3rd-order system crosses -180 phase and has finite gain margin."""
        freq = np.logspace(0, 6, 10000)
        # Three-pole system: K / ((s/w1 + 1) * (s/w2 + 1) * (s/w3 + 1))
        # Phase goes from 0 to -270, so it definitely crosses -180
        w1 = 2 * np.pi * 100
        w2 = 2 * np.pi * 1000
        w3 = 2 * np.pi * 10000
        K = 100.0  # enough gain to have a gain crossover
        s = 1j * 2 * np.pi * freq
        loop_gain = K / ((s / w1 + 1) * (s / w2 + 1) * (s / w3 + 1))

        result = compute_gain_margin(freq, loop_gain)
        assert result["gain_margin_db"] is not None
        assert result["is_stable"] is True
        assert result["gain_margin_db"] > 0
        assert result["phase_crossover_freq_hz"] is not None

    def test_no_phase_crossover(self):
        """A simple first-order system never reaches -180 phase, so GM is infinite."""
        freq = np.logspace(0, 6, 1000)
        s = 1j * 2 * np.pi * freq
        # First-order: 1/(1+s/wn) -- phase goes from 0 to -90
        loop_gain = 1.0 / (1 + s / (2 * np.pi * 1000))
        result = compute_gain_margin(freq, loop_gain)
        assert result["gain_margin_db"] == float("inf")
        assert result["is_stable"] is True

    def test_short_input(self):
        result = compute_gain_margin(np.array([1.0]), np.array([1.0 + 0j]))
        assert result["gain_margin_db"] is None


class TestPhaseMargin:
    def test_third_order_system(self):
        """A 3rd-order system with sufficient gain should have measurable phase margin."""
        freq = np.logspace(0, 6, 10000)
        w1 = 2 * np.pi * 100
        w2 = 2 * np.pi * 1000
        w3 = 2 * np.pi * 10000
        K = 100.0
        s = 1j * 2 * np.pi * freq
        loop_gain = K / ((s / w1 + 1) * (s / w2 + 1) * (s / w3 + 1))

        result = compute_phase_margin(freq, loop_gain)
        assert result["phase_margin_deg"] is not None
        assert result["is_stable"] is True
        assert result["phase_margin_deg"] > 0

    def test_all_gain_below_0db(self):
        """If gain is always below 0 dB, phase margin is infinite (system is stable)."""
        freq = np.logspace(0, 6, 1000)
        s = 1j * 2 * np.pi * freq
        # Very low gain system
        loop_gain = 0.001 / (1 + s / (2 * np.pi * 1000))
        result = compute_phase_margin(freq, loop_gain)
        assert result["phase_margin_deg"] == float("inf")
        assert result["is_stable"] is True
        assert result["gain_crossover_freq_hz"] is None

    def test_short_input(self):
        result = compute_phase_margin(np.array([1.0]), np.array([1.0 + 0j]))
        assert result["phase_margin_deg"] is None


class TestStabilityMetrics:
    def test_comprehensive_output(self):
        """compute_stability_metrics returns all expected fields."""
        freq = np.logspace(0, 6, 5000)
        w1 = 2 * np.pi * 100
        w2 = 2 * np.pi * 1000
        w3 = 2 * np.pi * 10000
        K = 100.0
        s = 1j * 2 * np.pi * freq
        loop_gain = K / ((s / w1 + 1) * (s / w2 + 1) * (s / w3 + 1))

        result = compute_stability_metrics(freq, loop_gain)
        assert "gain_margin" in result
        assert "phase_margin" in result
        assert "bode" in result
        assert "is_stable" in result
        assert len(result["bode"]["frequency_hz"]) == len(freq)

    def test_short_input_structure(self):
        result = compute_stability_metrics(np.array([]), np.array([]))
        assert result["is_stable"] is None
