"""Tests for the 5 new circuit templates (netlist + asc generator)."""

import pytest

from mcltspice.asc_generator import (
    AscSchematic,
    generate_boost_converter,
    generate_current_mirror,
    generate_instrumentation_amp,
    generate_sallen_key_lowpass,
    generate_transimpedance_amp,
)
from mcltspice.netlist import (
    Netlist,
    boost_converter,
    current_mirror,
    instrumentation_amplifier,
    sallen_key_lowpass,
    transimpedance_amplifier,
)

# ---------------------------------------------------------------------------
# Netlist template tests
# ---------------------------------------------------------------------------


class TestSallenKeyLowpassNetlist:
    def test_returns_netlist(self):
        n = sallen_key_lowpass()
        assert isinstance(n, Netlist)

    def test_component_count(self):
        n = sallen_key_lowpass()
        # V1, Vpos, Vneg, R1, R2, C1, C2, X1 = 8 components
        assert len(n.components) == 8

    def test_render_contains_key_components(self):
        text = sallen_key_lowpass().render()
        assert "R1" in text
        assert "R2" in text
        assert "C1" in text
        assert "C2" in text
        assert "X1" in text
        assert "LT1001" in text
        assert ".ac" in text

    def test_custom_params(self):
        n = sallen_key_lowpass(r1="4.7k", r2="4.7k", c1="22n", c2="22n")
        text = n.render()
        assert "4.7k" in text
        assert "22n" in text

    def test_has_backanno_and_end(self):
        text = sallen_key_lowpass().render()
        assert ".backanno" in text
        assert ".end" in text


class TestBoostConverterNetlist:
    def test_returns_netlist(self):
        n = boost_converter()
        assert isinstance(n, Netlist)

    def test_component_count(self):
        n = boost_converter()
        # Vin, Vgate, L1, M1, D1, Cout, Rload = 7 components
        assert len(n.components) == 7

    def test_render_contains_key_components(self):
        text = boost_converter().render()
        assert "L1" in text
        assert "M1" in text
        assert "D1" in text
        assert "Cout" in text
        assert "Rload" in text
        assert "PULSE(" in text
        assert ".tran" in text

    def test_custom_params(self):
        n = boost_converter(ind="22u", r_load="100", v_in="3.3", duty_cycle=0.6)
        text = n.render()
        assert "22u" in text
        assert "100" in text
        assert "3.3" in text

    def test_has_backanno_and_end(self):
        text = boost_converter().render()
        assert ".backanno" in text
        assert ".end" in text


class TestInstrumentationAmplifierNetlist:
    def test_returns_netlist(self):
        n = instrumentation_amplifier()
        assert isinstance(n, Netlist)

    def test_component_count(self):
        n = instrumentation_amplifier()
        # V1, V2, Vpos, Vneg, X1, X2, R1, R1b, Rgain, X3, R2, R3, R2b, R3b = 14
        assert len(n.components) == 14

    def test_render_contains_key_components(self):
        text = instrumentation_amplifier().render()
        assert "X1" in text
        assert "X2" in text
        assert "X3" in text
        assert "Rgain" in text
        assert "LT1001" in text
        assert ".ac" in text

    def test_custom_params(self):
        n = instrumentation_amplifier(r1="20k", r_gain="1k")
        text = n.render()
        assert "20k" in text
        assert "1k" in text

    def test_has_backanno_and_end(self):
        text = instrumentation_amplifier().render()
        assert ".backanno" in text
        assert ".end" in text


class TestCurrentMirrorNetlist:
    def test_returns_netlist(self):
        n = current_mirror()
        assert isinstance(n, Netlist)

    def test_component_count(self):
        n = current_mirror()
        # Vcc, Rref, Rload, Q1, Q2 = 5 components
        assert len(n.components) == 5

    def test_render_contains_key_components(self):
        text = current_mirror().render()
        assert "Q1" in text
        assert "Q2" in text
        assert "Rref" in text
        assert "Rload" in text
        assert "2N2222" in text
        assert ".op" in text
        assert ".tran" in text

    def test_custom_params(self):
        n = current_mirror(r_ref="4.7k", r_load="2.2k", vcc="5")
        text = n.render()
        assert "4.7k" in text
        assert "2.2k" in text
        assert "5" in text

    def test_has_backanno_and_end(self):
        text = current_mirror().render()
        assert ".backanno" in text
        assert ".end" in text


class TestTransimpedanceAmplifierNetlist:
    def test_returns_netlist(self):
        n = transimpedance_amplifier()
        assert isinstance(n, Netlist)

    def test_component_count(self):
        n = transimpedance_amplifier()
        # I1, Vpos, Vneg, Rf, Cf, X1 = 6 components
        assert len(n.components) == 6

    def test_render_contains_key_components(self):
        text = transimpedance_amplifier().render()
        assert "I1" in text
        assert "Rf" in text
        assert "Cf" in text
        assert "X1" in text
        assert "LT1001" in text
        assert ".ac" in text

    def test_custom_params(self):
        n = transimpedance_amplifier(rf="1Meg", cf="0.5p", i_source="10u")
        text = n.render()
        assert "1Meg" in text
        assert "0.5p" in text
        assert "10u" in text

    def test_has_backanno_and_end(self):
        text = transimpedance_amplifier().render()
        assert ".backanno" in text
        assert ".end" in text


# ---------------------------------------------------------------------------
# ASC generator template tests
# ---------------------------------------------------------------------------


class TestSallenKeyLowpassAsc:
    def test_returns_schematic(self):
        sch = generate_sallen_key_lowpass()
        assert isinstance(sch, AscSchematic)

    def test_render_valid(self):
        text = generate_sallen_key_lowpass().render()
        assert text.startswith("Version 4\n")
        assert "SHEET" in text
        assert len(text) > 100

    def test_contains_expected_symbols(self):
        text = generate_sallen_key_lowpass().render()
        assert "SYMBOL res" in text
        assert "SYMBOL cap" in text
        assert "SYMBOL OpAmps/UniversalOpamp2" in text
        assert "SYMBOL voltage" in text

    def test_custom_params(self):
        text = generate_sallen_key_lowpass(r1="4.7k", c1="22n").render()
        assert "4.7k" in text
        assert "22n" in text

    def test_has_simulation_directive(self):
        text = generate_sallen_key_lowpass().render()
        assert ".ac" in text


class TestBoostConverterAsc:
    def test_returns_schematic(self):
        sch = generate_boost_converter()
        assert isinstance(sch, AscSchematic)

    def test_render_valid(self):
        text = generate_boost_converter().render()
        assert text.startswith("Version 4\n")
        assert "SHEET" in text
        assert len(text) > 100

    def test_contains_expected_symbols(self):
        text = generate_boost_converter().render()
        assert "SYMBOL ind" in text
        assert "SYMBOL nmos" in text
        assert "SYMBOL diode" in text
        assert "SYMBOL cap" in text
        assert "SYMBOL res" in text

    def test_custom_params(self):
        text = generate_boost_converter(ind="22u", r_load="100").render()
        assert "22u" in text
        assert "100" in text

    def test_has_simulation_directive(self):
        text = generate_boost_converter().render()
        assert ".tran" in text


class TestInstrumentationAmpAsc:
    def test_returns_schematic(self):
        sch = generate_instrumentation_amp()
        assert isinstance(sch, AscSchematic)

    def test_render_valid(self):
        text = generate_instrumentation_amp().render()
        assert text.startswith("Version 4\n")
        assert "SHEET" in text
        assert len(text) > 100

    def test_contains_expected_symbols(self):
        text = generate_instrumentation_amp().render()
        # Should have 3 opamps
        assert text.count("SYMBOL OpAmps/UniversalOpamp2") == 3
        # Should have multiple resistors
        assert text.count("SYMBOL res") >= 7

    def test_custom_params(self):
        text = generate_instrumentation_amp(r1="20k", r_gain="1k").render()
        assert "20k" in text
        assert "1k" in text

    def test_has_simulation_directive(self):
        text = generate_instrumentation_amp().render()
        assert ".ac" in text


class TestCurrentMirrorAsc:
    def test_returns_schematic(self):
        sch = generate_current_mirror()
        assert isinstance(sch, AscSchematic)

    def test_render_valid(self):
        text = generate_current_mirror().render()
        assert text.startswith("Version 4\n")
        assert "SHEET" in text
        assert len(text) > 100

    def test_contains_expected_symbols(self):
        text = generate_current_mirror().render()
        # Should have 2 NPN transistors
        assert text.count("SYMBOL npn") == 2
        assert "SYMBOL res" in text
        assert "SYMBOL voltage" in text

    def test_custom_params(self):
        text = generate_current_mirror(r_ref="4.7k", r_load="2.2k").render()
        assert "4.7k" in text
        assert "2.2k" in text

    def test_has_simulation_directive(self):
        text = generate_current_mirror().render()
        assert ".op" in text
        assert ".tran" in text


class TestTransimpedanceAmpAsc:
    def test_returns_schematic(self):
        sch = generate_transimpedance_amp()
        assert isinstance(sch, AscSchematic)

    def test_render_valid(self):
        text = generate_transimpedance_amp().render()
        assert text.startswith("Version 4\n")
        assert "SHEET" in text
        assert len(text) > 100

    def test_contains_expected_symbols(self):
        text = generate_transimpedance_amp().render()
        assert "SYMBOL OpAmps/UniversalOpamp2" in text
        assert "SYMBOL res" in text
        assert "SYMBOL cap" in text

    def test_custom_params(self):
        text = generate_transimpedance_amp(rf="1Meg", cf="0.5p").render()
        assert "1Meg" in text
        assert "0.5p" in text

    def test_has_simulation_directive(self):
        text = generate_transimpedance_amp().render()
        assert ".ac" in text


# ---------------------------------------------------------------------------
# Parametrized cross-cutting tests for all 5 new netlist templates
# ---------------------------------------------------------------------------


class TestNewNetlistTemplatesCommon:
    @pytest.mark.parametrize(
        "factory",
        [
            sallen_key_lowpass,
            boost_converter,
            instrumentation_amplifier,
            current_mirror,
            transimpedance_amplifier,
        ],
    )
    def test_returns_netlist(self, factory):
        assert isinstance(factory(), Netlist)

    @pytest.mark.parametrize(
        "factory",
        [
            sallen_key_lowpass,
            boost_converter,
            instrumentation_amplifier,
            current_mirror,
            transimpedance_amplifier,
        ],
    )
    def test_has_backanno_and_end(self, factory):
        text = factory().render()
        assert ".backanno" in text
        assert ".end" in text

    @pytest.mark.parametrize(
        "factory",
        [
            sallen_key_lowpass,
            boost_converter,
            instrumentation_amplifier,
            current_mirror,
            transimpedance_amplifier,
        ],
    )
    def test_has_components(self, factory):
        n = factory()
        assert len(n.components) > 0

    @pytest.mark.parametrize(
        "factory",
        [
            sallen_key_lowpass,
            boost_converter,
            instrumentation_amplifier,
            current_mirror,
            transimpedance_amplifier,
        ],
    )
    def test_has_sim_directive(self, factory):
        text = factory().render()
        sim_types = [".tran", ".ac", ".dc", ".op", ".noise", ".tf"]
        assert any(sim in text.lower() for sim in sim_types), (
            f"No simulation directive found in {factory.__name__}"
        )


# ---------------------------------------------------------------------------
# Parametrized cross-cutting tests for all 5 new ASC templates
# ---------------------------------------------------------------------------


class TestNewAscTemplatesCommon:
    @pytest.mark.parametrize(
        "factory",
        [
            generate_sallen_key_lowpass,
            generate_boost_converter,
            generate_instrumentation_amp,
            generate_current_mirror,
            generate_transimpedance_amp,
        ],
    )
    def test_returns_schematic(self, factory):
        assert isinstance(factory(), AscSchematic)

    @pytest.mark.parametrize(
        "factory",
        [
            generate_sallen_key_lowpass,
            generate_boost_converter,
            generate_instrumentation_amp,
            generate_current_mirror,
            generate_transimpedance_amp,
        ],
    )
    def test_render_nonempty(self, factory):
        text = factory().render()
        assert len(text) > 50
        assert "SYMBOL" in text

    @pytest.mark.parametrize(
        "factory",
        [
            generate_sallen_key_lowpass,
            generate_boost_converter,
            generate_instrumentation_amp,
            generate_current_mirror,
            generate_transimpedance_amp,
        ],
    )
    def test_has_version_and_sheet(self, factory):
        text = factory().render()
        assert "Version 4" in text
        assert "SHEET" in text
