# 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, generate_spectre
from analogpy.devices import resistor, capacitor, vsource
from analogpy.devices import vsource


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("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("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 = generate_spectre(tb)
        result = simulator.run_netlist(netlist, "dc_divider")

        assert result.success, f"Simulation failed: {result.stderr}"
        assert result.output_dir is not None
        # For remote execution, results stay on remote server (no local files)
        # For local execution, check that output was generated
        if result.log_file is not None or len(result.raw_files) > 0:
            pass  # Local mode - files available
        else:
            # Remote mode - verify simulation completed 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 = generate_spectre(tb)
        result = simulator.run_netlist(netlist, "rc_dc")

        assert result.success, f"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 = generate_spectre(tb)
        result = simulator.run_netlist(netlist, "divider_value")

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

        # 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()