"""Tests for raw_parser module: run boundaries, variable lookup, run slicing."""

import numpy as np
import pytest

from mcltspice.raw_parser import _detect_run_boundaries


class TestDetectRunBoundaries:
    def test_single_run(self):
        """Monotonically increasing time -> single run starting at 0."""
        x = np.linspace(0, 1e-3, 500)
        boundaries = _detect_run_boundaries(x)
        assert boundaries == [0]

    def test_multi_run(self):
        """Three runs: time resets to near-zero at each boundary."""
        run1 = np.linspace(0, 1e-3, 100)
        run2 = np.linspace(0, 1e-3, 100)
        run3 = np.linspace(0, 1e-3, 100)
        x = np.concatenate([run1, run2, run3])
        boundaries = _detect_run_boundaries(x)
        assert len(boundaries) == 3
        assert boundaries[0] == 0
        assert boundaries[1] == 100
        assert boundaries[2] == 200

    def test_complex_ac(self):
        """AC analysis with complex frequency axis that resets."""
        run1 = np.logspace(0, 6, 50).astype(np.complex128)
        run2 = np.logspace(0, 6, 50).astype(np.complex128)
        x = np.concatenate([run1, run2])
        boundaries = _detect_run_boundaries(x)
        assert len(boundaries) == 2
        assert boundaries[0] == 0
        assert boundaries[1] == 50

    def test_single_point(self):
        """Single data point -> one run."""
        boundaries = _detect_run_boundaries(np.array([0.0]))
        assert boundaries == [0]


class TestRawFileGetVariable:
    def test_exact_match(self, mock_rawfile):
        """Exact name match returns correct data."""
        result = mock_rawfile.get_variable("V(out)")
        assert result is not None
        assert len(result) == mock_rawfile.points

    def test_case_insensitive(self, mock_rawfile):
        """Variable lookup is case-insensitive (partial match)."""
        result = mock_rawfile.get_variable("v(out)")
        assert result is not None

    def test_partial_match(self, mock_rawfile):
        """Substring match should work: 'out' matches 'V(out)'."""
        result = mock_rawfile.get_variable("out")
        assert result is not None

    def test_missing_variable(self, mock_rawfile):
        """Non-existent variable returns None."""
        result = mock_rawfile.get_variable("V(nonexistent)")
        assert result is None

    def test_get_time(self, mock_rawfile):
        result = mock_rawfile.get_time()
        assert result is not None
        assert len(result) == mock_rawfile.points


class TestRawFileRunData:
    def test_get_run_data_slicing(self, mock_rawfile_stepped):
        """Extracting a single run produces correct point count."""
        run0 = mock_rawfile_stepped.get_run_data(0)
        assert run0.points == 100
        assert run0.n_runs == 1
        assert run0.is_stepped is False

    def test_get_run_data_values(self, mock_rawfile_stepped):
        """Each run has the expected amplitude scaling."""
        for i in range(3):
            run = mock_rawfile_stepped.get_run_data(i)
            sig = run.get_variable("V(out)")
            # Peak amplitude should be approximately (i+1)
            assert float(np.max(np.abs(sig))) == pytest.approx(i + 1, rel=0.1)

    def test_is_stepped(self, mock_rawfile_stepped, mock_rawfile):
        assert mock_rawfile_stepped.is_stepped is True
        assert mock_rawfile.is_stepped is False

    def test_get_variable_with_run(self, mock_rawfile_stepped):
        """get_variable with run= parameter slices correctly."""
        v_run1 = mock_rawfile_stepped.get_variable("V(out)", run=1)
        assert v_run1 is not None
        assert len(v_run1) == 100

    def test_non_stepped_get_run_data(self, mock_rawfile):
        """Getting run data from non-stepped file returns self."""
        run = mock_rawfile.get_run_data(0)
        assert run.points == mock_rawfile.points
