# Copyright 2026 Gaofeng Fan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Simulation tests for analogpy.

These tests verify that generated netlists are valid and produce correct results.

Test Levels:
1. Syntax checks - Always run, use basic Python validation if no simulator
2. Basic simulation - Requires @pytest.mark.simulator, runs actual simulation
3. Result validation - Compares simulation results against golden values

To run simulation tests:
    pytest tests/test_simulation.py -v

To skip simulation tests:
    ANALOGPY_SKIP_SIMULATION=1 pytest tests/test_simulation.py -v

For simulator setup, see README.md.
"""

import pytest
from analogpy import Testbench, Circuit, DC, AC, Transient, Noise, STB
from analogpy import PSS, PAC, PNoise, PXF, HB, SP, XF, PZ, FFT
from analogpy import generate_spectre, generate_ngspice
from analogpy.devices import resistor, capacitor, inductor, vsource


def _netlist(tb, simulator):
    """Generate netlist in the appropriate format for the current simulator mode."""
    if simulator.config.mode == "ngspice":
        return generate_ngspice(tb)
    return generate_spectre(tb)


class TestSyntaxCheck:
    """
    Level 1: Syntax validation tests.

    These tests verify netlist syntax without requiring a simulator.
    Uses Python-based validation as fallback.
    """

    def test_simple_resistor_syntax(self, syntax_checker):
        """Test basic resistor circuit syntax."""
        tb = Testbench("test_resistor")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_vin", p=tb.net("vin"), n=gnd, dc=1.0)
        vin = tb.net("vin")
        vout = tb.net("vout")

        tb.add_instance(resistor, "R1", p=vin, n=vout, r=1000)
        tb.add_instance(resistor, "R2", p=vout, n=gnd, r=1000)
        tb.add_analysis(DC())

        netlist = generate_spectre(tb)
        result = syntax_checker.check_syntax(netlist, "simple_resistor")

        assert result.success, f"Syntax check failed: {result.stderr}"

    def test_subcircuit_syntax(self, syntax_checker):
        """Test subcircuit definition and instantiation syntax."""
        # Define a simple subcircuit
        divider = Circuit(name="voltage_divider", ports=["IN", "OUT", "GND"])
        div_in = divider.net("IN")
        div_out = divider.net("OUT")
        div_gnd = divider.net("GND")
        divider.add_instance(resistor, "R1", p=div_in, n=div_out, r=10000)
        divider.add_instance(resistor, "R2", p=div_out, n=div_gnd, r=10000)

        # Create testbench
        tb = Testbench("test_subckt")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_vin", p=tb.net("vin"), n=gnd, dc=3.3)
        vin = tb.net("vin")
        vout = tb.net("vout")

        tb.add_instance(divider, IN=vin, OUT=vout, GND=gnd)
        tb.add_analysis(DC())

        netlist = generate_spectre(tb)
        result = syntax_checker.check_syntax(netlist, "subcircuit")

        assert result.success, f"Syntax check failed: {result.stderr}"

    def test_multiple_instances_syntax(self, syntax_checker):
        """Test multiple subcircuit instances with auto-naming."""
        # Define subcircuit
        buf = Circuit(name="buffer", ports=["IN", "OUT"])
        buf.add_instance(resistor, "R1", p=buf.net("IN"), n=buf.net("OUT"), r=100)

        tb = Testbench("test_multi_inst")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_v1", p=tb.net("v1"), n=gnd, dc=1.0)
        v1 = tb.net("v1")
        n1 = tb.net("n1")
        n2 = tb.net("n2")
        n3 = tb.net("n3")

        # Auto-generated instance names
        tb.add_instance(buf, IN=v1, OUT=n1)   # I_buffer
        tb.add_instance(buf, IN=n1, OUT=n2)   # I_buffer_1
        tb.add_instance(buf, IN=n2, OUT=n3)   # I_buffer_2

        tb.add_analysis(DC())

        netlist = generate_spectre(tb)
        result = syntax_checker.check_syntax(netlist, "multi_instance")

        assert result.success, f"Syntax check failed: {result.stderr}"
        # Verify auto-generated names appear in netlist
        assert "I_buffer" in netlist
        assert "I_buffer_1" in netlist
        assert "I_buffer_2" in netlist

    def test_invalid_syntax_detected(self, syntax_checker):
        """Test that invalid syntax is detected."""
        # Manually create invalid netlist (unmatched subckt)
        invalid_netlist = """
simulator lang=spectre
global 0

subckt broken_circuit (A B)
    R1 (A B) resistor r=1k
// Missing 'ends broken_circuit'

I1 (net1 0) broken_circuit
"""
        result = syntax_checker.check_syntax(invalid_netlist, "invalid")
        assert not result.success, "Should detect missing 'ends'"


@pytest.mark.simulator
class TestBasicSimulation:
    """
    Level 2: Basic simulation tests.

    These tests require a working Spectre simulator (local or remote).
    They verify that simulations run successfully and produce output files.
    """

    def test_dc_resistor_divider(self, simulator):
        """Test DC simulation of resistor divider."""
        tb = Testbench("dc_divider")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_vin", p=tb.net("vin"), n=gnd, dc=2.0)
        vin = tb.net("vin")
        vout = tb.net("vout")

        tb.add_instance(resistor, "R1", p=vin, n=vout, r=1000)
        tb.add_instance(resistor, "R2", p=vout, n=gnd, r=1000)
        tb.add_analysis(DC(name="dcOp"))
        tb.save("vout")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "dc_divider")

        assert result.success, f"Simulation failed: {result.stderr}\nstdout: {result.stdout}"
        assert result.output_dir is not None
        if result.log_file is not None or len(result.raw_files) > 0:
            pass  # Local/ngspice mode - files available
        else:
            # Remote Spectre mode - verify completion in stdout
            assert "spectre completes" in result.stdout.lower()

    def test_rc_circuit_dc(self, simulator):
        """Test DC simulation with RC circuit."""
        tb = Testbench("rc_dc")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_vin", p=tb.net("vin"), n=gnd, dc=1.0)
        vin = tb.net("vin")
        vout = tb.net("vout")

        tb.add_instance(resistor, "R1", p=vin, n=vout, r=10000)
        tb.add_instance(capacitor, "C1", p=vout, n=gnd, c=1e-12)
        tb.add_analysis(DC(name="dcOp"))

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "rc_dc")

        assert result.success, f"Simulation failed: {result.stderr}\nstdout: {result.stdout}"


@pytest.mark.simulator
class TestAnalysisSimulation:
    """
    Level 2: Analysis simulation tests.

    These tests verify that different analysis types run successfully
    in both Spectre and ngspice simulators.

    Test coverage:
    - DC: Operating point analysis
    - AC: Small-signal frequency analysis
    - Transient: Time-domain simulation
    - Noise: Noise analysis
    - STB: Stability analysis (Spectre only)
    - PSS: Periodic steady-state (Spectre, ngspice experimental)
    - PAC: Periodic AC (Spectre, ngspice experimental)
    - PNoise: Periodic noise (Spectre, ngspice experimental)
    - PXF: Periodic transfer function (Spectre only)
    - HB: Harmonic balance (Spectre only)
    - SP: S-parameter (Spectre only)
    - XF: Transfer function
    - PZ: Pole-zero analysis
    - FFT: FFT analysis (ngspice only)
    """

    def _simple_rc_circuit(self):
        """Create a simple RC circuit for testing."""
        tb = Testbench("rc_analysis")
        gnd = tb.gnd()
        vin = tb.net("vin")
        vout = tb.net("vout")

        # Voltage source
        tb.add_instance(vsource, "VIN", p=vin, n=gnd, dc=1.0)
        # RC network
        tb.add_instance(resistor, "R1", p=vin, n=vout, r=1e3)
        tb.add_instance(capacitor, "C1", p=vout, n=gnd, c=1e-9)

        return tb

    def test_dc_analysis(self, simulator):
        """Test DC operating point analysis."""
        tb = self._simple_rc_circuit()
        tb.add_analysis(DC())
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "dc_analysis")

        assert result.success, f"DC simulation failed: {result.stderr}"

    def test_ac_analysis(self, simulator):
        """Test AC small-signal analysis."""
        tb = self._simple_rc_circuit()
        tb.add_analysis(AC(start=1, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "ac_analysis")

        assert result.success, f"AC simulation failed: {result.stderr}"

    def test_tran_analysis(self, simulator):
        """Test Transient time-domain analysis."""
        tb = self._simple_rc_circuit()
        tb.add_analysis(Transient(stop=1e-5, step=1e-7))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "tran_analysis")

        assert result.success, f"Transient simulation failed: {result.stderr}"

    def test_noise_analysis(self, simulator):
        """Test Noise analysis."""
        tb = self._simple_rc_circuit()
        tb.add_analysis(Noise(output="vout", input_src="VIN", start=1, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "noise_analysis")

        assert result.success, f"Noise simulation failed: {result.stderr}"

    def test_stb_analysis_spectre(self, simulator):
        """Test STB stability analysis (Spectre only)."""
        # Skip for ngspice
        if simulator.config.mode == "ngspice":
            pytest.skip("STB not supported in ngspice")

        tb = self._simple_rc_circuit()
        tb.add_analysis(STB(probe="vout", start=1, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "stb_analysis")

        assert result.success, f"STB simulation failed: {result.stderr}"

    def test_pss_analysis(self, simulator):
        """Test PSS periodic steady-state analysis."""
        # Create oscillator circuit for PSS
        tb = Testbench("pss_test")
        gnd = tb.gnd()
        tb.add_instance(vsource, "VIN", p=tb.net("vin"), n=gnd, dc=0, ac=1)
        tb.add_instance(resistor, "R1", p=tb.net("vin"), n=tb.net("vout"), r=1e3)
        tb.add_instance(capacitor, "C1", p=tb.net("vout"), n=gnd, c=1e-9)

        tb.add_analysis(PSS(fund=1e5, harms=5, tstab=1e-5))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "pss_analysis")

        assert result.success, f"PSS simulation failed: {result.stderr}"

    def test_pac_analysis(self, simulator):
        """Test PAC periodic AC analysis."""
        # Create oscillator circuit for PAC
        tb = Testbench("pac_test")
        gnd = tb.gnd()
        tb.add_instance(vsource, "VIN", p=tb.net("vin"), n=gnd, dc=0, ac=1)
        tb.add_instance(resistor, "R1", p=tb.net("vin"), n=tb.net("vout"), r=1e3)
        tb.add_instance(capacitor, "C1", p=tb.net("vout"), n=gnd, c=1e-9)

        tb.add_analysis(PSS(fund=1e5, harms=5))
        tb.add_analysis(PAC(start=1e3, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "pac_analysis")

        assert result.success, f"PAC simulation failed: {result.stderr}"

    def test_pnoise_analysis(self, simulator):
        """Test PNoise periodic noise analysis."""
        tb = self._simple_rc_circuit()

        tb.add_analysis(PSS(fund=1e5, harms=5))
        tb.add_analysis(PNoise(start=1e3, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "pnoise_analysis")

        assert result.success, f"PNoise simulation failed: {result.stderr}"

    def test_pxf_analysis_spectre(self, simulator):
        """Test PXF periodic transfer function (Spectre only)."""
        if simulator.config.mode == "ngspice":
            pytest.skip("PXF not supported in ngspice")

        tb = self._simple_rc_circuit()
        tb.add_analysis(PSS(fund=1e5))
        tb.add_analysis(PXF(start=1e3, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "pxf_analysis")

        assert result.success, f"PXF simulation failed: {result.stderr}"

    def test_hb_analysis_spectre(self, simulator):
        """Test HB harmonic balance analysis (Spectre only)."""
        if simulator.config.mode == "ngspice":
            pytest.skip("HB not supported in ngspice")

        tb = self._simple_rc_circuit()
        tb.add_analysis(HB(funds=[1e5], maxharms=[5]))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "hb_analysis")

        assert result.success, f"HB simulation failed: {result.stderr}"

    def test_sp_analysis_spectre(self, simulator):
        """Test SP S-parameter analysis (Spectre only)."""
        if simulator.config.mode == "ngspice":
            pytest.skip("SP not supported in ngspice")

        tb = self._simple_rc_circuit()
        tb.add_analysis(SP(start=1e6, stop=1e9, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "sp_analysis")

        assert result.success, f"SP simulation failed: {result.stderr}"

    def test_xf_analysis(self, simulator):
        """Test XF transfer function analysis."""
        tb = self._simple_rc_circuit()
        tb.add_analysis(XF(start=1, stop=1e6, points=10))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "xf_analysis")

        assert result.success, f"XF simulation failed: {result.stderr}"

    def test_pz_analysis(self, simulator):
        """Test PZ pole-zero analysis."""
        tb = self._simple_rc_circuit()
        tb.add_analysis(PZ())
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "pz_analysis")

        assert result.success, f"PZ simulation failed: {result.stderr}"

    def test_fft_analysis_ngspice(self, simulator):
        """Test FFT analysis (ngspice only)."""
        if simulator.config.mode == "spectre":
            pytest.skip("FFT not supported in Spectre")

        tb = self._simple_rc_circuit()
        tb.add_analysis(FFT(stop=1e-5, np=1024))
        tb.save("v(vout)")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "fft_analysis")

        assert result.success, f"FFT simulation failed: {result.stderr}"


@pytest.mark.simulator
class TestResultValidation:
    """
    Level 3: Result validation tests.

    These tests compare simulation results against known golden values.
    Requires working simulator and result parsing capability.
    """

    def test_voltage_divider_value(self, simulator):
        """Test that voltage divider produces expected output."""
        tb = Testbench("divider_value")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_vin", p=tb.net("vin"), n=gnd, dc=2.0)
        vin = tb.net("vin")
        vout = tb.net("vout")

        # Equal resistors should give Vout = Vin/2 = 1.0V
        tb.add_instance(resistor, "R1", p=vin, n=vout, r=1000)
        tb.add_instance(resistor, "R2", p=vout, n=gnd, r=1000)
        tb.add_analysis(DC(name="dcOp"))
        tb.save("vout")

        netlist = _netlist(tb, simulator)
        result = simulator.run_netlist(netlist, "divider_value")

        assert result.success, f"Simulation failed: {result.stderr}\nstdout: {result.stdout}"

        # TODO: Parse output and verify vout ≈ 1.0V
        # dc_values = result.get_dc_values()
        # assert abs(dc_values.get("vout", 0) - 1.0) < 0.01

    def test_series_resistors(self, simulator):
        """Test series resistor combination."""
        tb = Testbench("series_r")
        gnd = tb.gnd()
        tb.add_instance(vsource, instance_name="I_vin", p=tb.net("vin"), n=gnd, dc=3.0)
        vin = tb.net("vin")
        v1 = tb.net("v1")
        v2 = tb.net("v2")

        # Three 1k resistors in series: V1=2V, V2=1V
        tb.add_instance(resistor, "R1", p=vin, n=v1, r=1000)
        tb.add_instance(resistor, "R2", p=v1, n=v2, r=1000)
        tb.add_instance(resistor, "R3", p=v2, n=gnd, r=1000)
        tb.add_analysis(DC(name="dcOp"))
        tb.save("v1")
        tb.save("v2")

        netlist = generate_spectre(tb)
        result = simulator.run_netlist(netlist, "series_r")

        assert result.success, f"Simulation failed: {result.stderr}"

        # TODO: Verify v1 ≈ 2.0V, v2 ≈ 1.0V


class TestSimulatorConfig:
    """Tests for simulator configuration detection."""

    def test_config_detection(self, simulator_config):
        """Test that simulator config is properly detected."""
        assert simulator_config.mode in ("local", "remote", "unavailable")
        assert simulator_config.workdir is not None

    def test_config_properties(self, simulator_config):
        """Test config helper properties."""
        if simulator_config.mode == "local":
            assert simulator_config.spectre_path is not None
            assert simulator_config.is_available
        elif simulator_config.mode == "remote":
            assert simulator_config.remote_host is not None
            assert simulator_config.remote_user is not None
            assert simulator_config.is_available
        else:
            assert not simulator_config.is_available
            assert "README" in simulator_config.get_skip_reason()