# 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.

"""Tests for Testbench and Analysis classes."""

import re
from unittest.mock import patch
from analogpy import (
    Testbench, Circuit, nmos, pmos, generate_spectre,
    DC, AC, Transient, Noise
)
from analogpy.devices import vsource


def make_inverter():
    """Helper to create an inverter circuit."""
    inv = Circuit("inverter", ports=["in", "out", "vdd", "vss"])
    inv.add_instance(nmos, "MN", d=inv.net("out"), g=inv.net("in"),
                    s=inv.net("vss"), b=inv.net("vss"), w=1e-6, l=180e-9)
    inv.add_instance(pmos, "MP", d=inv.net("out"), g=inv.net("in"),
                    s=inv.net("vdd"), b=inv.net("vdd"), w=2e-6, l=180e-9)
    return inv


def test_testbench_basic():
    """Test basic Testbench creation with explicit name."""
    tb = Testbench("tb_test")
    assert tb.name == "tb_test"
    assert len(tb.analyses) == 0
    assert tb.options.temp == 27


def test_testbench_auto_name_from_filename():
    """Test Testbench auto-detects name from caller's filename."""
    tb = Testbench()
    # Called from test_testbench.py, should auto-detect "test_testbench"
    assert tb.name == "test_testbench"


def test_testbench_auto_name_datetime_fallback():
    """Test Testbench falls back to datetime when filename detection fails."""
    # Mock _get_caller_filename to return None (simulates REPL/Jupyter)
    with patch('analogpy.testbench._get_caller_filename', return_value=None):
        tb = Testbench()
        # Should match pattern: testbench_YYYYMMDD_HHMMSS
        assert re.match(r'testbench_\d{8}_\d{6}', tb.name)


def test_testbench_with_dut():
    """Test Testbench with instantiated DUT."""
    inv = make_inverter()

    tb = Testbench("tb_inverter")
    vin = tb.net("vin")
    vout = tb.net("vout")
    tb.add_instance(vsource, instance_name="I_vdd", p=tb.net("vdd"), n=tb.gnd(), dc=1.8)
    vdd = tb.net("vdd")
    gnd = tb.gnd()  # Global ground "0" at testbench level

    tb.add_instance(inv, "X1",
                   **{"in": vin, "out": vout, "vdd": vdd, "vss": gnd})

    assert len(tb.subcircuit_instances) == 2 # Changed from 1 to 2


def test_dc_analysis():
    """Test DC analysis."""
    dc = DC()
    assert dc.name == "dcOp"

    spectre_str = dc.to_spectre()
    assert "dcOp dc" in spectre_str
    assert "maxiters=150" in spectre_str


def test_ac_analysis():
    """Test AC analysis."""
    ac = AC(start=1, stop=1e9, points=100)
    spectre_str = ac.to_spectre()

    assert "ac start=1" in spectre_str
    assert "stop=1000000000.0" in spectre_str
    assert "dec=100" in spectre_str


def test_transient_analysis():
    """Test Transient analysis."""
    tran = Transient(stop=1e-6)
    spectre_str = tran.to_spectre()

    assert "tran tran" in spectre_str
    assert "stop=1e-06" in spectre_str


def test_testbench_with_analyses():
    """Test Testbench with multiple analyses."""
    inv = make_inverter()

    tb = Testbench("tb_inverter")
    tb.add_instance(vsource, instance_name="I_vdd", p=tb.net("vdd"), n=tb.gnd(), dc=1.8)
    vdd = tb.net("vdd")
    gnd = tb.gnd()  # Global ground "0" at testbench level
    vin = tb.net("vin")
    vout = tb.net("vout")

    tb.add_instance(inv, "X1",
                   **{"in": vin, "out": vout, "vdd": vdd, "vss": gnd})

    # Add analyses
    tb.add_analysis(DC())
    tb.add_analysis(AC(start=1, stop=1e9))
    tb.add_analysis(Transient(stop=1e-6))

    assert len(tb.analyses) == 3

    netlist = generate_spectre(tb)
    print("\n=== Testbench with Analyses ===")
    print(netlist)

    # Verify content
    assert "simulator lang=spectre" in netlist
    assert "global 0" in netlist
    assert "simulatorOptions options" in netlist
    assert "dcOp dc" in netlist
    assert "ac ac" in netlist
    assert "tran tran" in netlist


def test_testbench_options():
    """Test Testbench simulator options."""
    tb = Testbench("tb_test")
    tb.set_options(temp=85, reltol=1e-4)
    tb.set_temp(-40)

    assert tb.options.temp == -40
    assert tb.options.reltol == 1e-4


def test_behavioral_model():
    """Test adding behavioral models."""
    tb = Testbench("tb_test")
    tb.behavioral_model("/path/to/model.va")
    tb.behavioral_model("/path/to/another.va")

    assert len(tb.behavioral_models) == 2

    netlist = generate_spectre(tb)
    assert 'ahdl_include "/path/to/model.va"' in netlist
    assert 'ahdl_include "/path/to/another.va"' in netlist


def test_ngspice_analysis():
    """Test ngspice output for analyses."""
    dc = DC()
    ac = AC(start=1, stop=1e9, points=100)
    tran = Transient(stop=1e-6)

    assert dc.to_ngspice() == ".op"
    assert "dec" in ac.to_ngspice()
    assert ".tran" in tran.to_ngspice()


def test_complete_testbench():
    """Test complete testbench generation."""
    inv = make_inverter()

    tb = Testbench("tb_inv_complete")

    # Setup
    tb.add_instance(vsource, instance_name="I_vdd", p=tb.net("vdd"), n=tb.gnd(), dc=1.8)
    vdd = tb.net("vdd")
    gnd = tb.gnd()  # Global ground "0" at testbench level
    vin = tb.net("vin")
    vout = tb.net("vout")

    # DUT
    tb.add_instance(inv, "X_INV",
                   **{"in": vin, "out": vout, "vdd": vdd, "vss": gnd})

    # Options
    tb.set_temp(27)
    tb.set_options(reltol=1e-3, vabstol=1e-6)

    # Analyses
    tb.add_analysis(DC(save_op=True))
    tb.add_analysis(AC(start=1, stop=10e9, points=200, variation="dec"))
    tb.add_analysis(Transient(stop=100e-9))

    netlist = generate_spectre(tb)
    print("\n=== Complete Testbench ===")
    print(netlist)

    # Comprehensive checks
    assert "tb_inv_complete" in netlist
    assert "subckt inverter" in netlist
    assert "X_INV (vin vout vdd 0) inverter" in netlist
    assert "I_vdd (vdd 0) vsource dc=1.8" in netlist # Changed from "Vvdd (vdd 0) vsource dc=1.8"
    assert "simulatorOptions options" in netlist
    assert "dcOp dc" in netlist
    assert "ac ac" in netlist
    assert "tran tran" in netlist


if __name__ == "__main__":
    test_testbench_basic()
    print("test_testbench_basic passed")

    test_testbench_auto_name_from_filename()
    print("test_testbench_auto_name_from_filename passed")

    test_testbench_auto_name_datetime_fallback()
    print("test_testbench_auto_name_datetime_fallback passed")

    test_testbench_with_dut()
    print("test_testbench_with_dut passed")

    test_dc_analysis()
    print("test_dc_analysis passed")

    test_ac_analysis()
    print("test_ac_analysis passed")

    test_transient_analysis()
    print("test_transient_analysis passed")

    test_testbench_with_analyses()
    print("test_testbench_with_analyses passed")

    test_testbench_options()
    print("test_testbench_options passed")

    test_behavioral_model()
    print("test_behavioral_model passed")

    test_ngspice_analysis()
    print("test_ngspice_analysis passed")

    test_complete_testbench()
    print("test_complete_testbench passed")

    print("\nAll testbench tests passed!")
