"""Tests for asc_generator module: pin positioning, schematic rendering, templates."""

import pytest

from mcltspice.asc_generator import (
    _PIN_OFFSETS,
    AscSchematic,
    _rotate,
    generate_inverting_amp,
    generate_rc_lowpass,
    generate_voltage_divider,
    pin_position,
)


class TestPinPosition:
    @pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
    def test_r0_returns_offset_plus_origin(self, symbol):
        """At R0, pin position = origin + raw offset."""
        cx, cy = 160, 80
        for pin_idx in range(2):
            px, py = pin_position(symbol, pin_idx, cx, cy, rotation=0)
            offsets = _PIN_OFFSETS[symbol]
            ox, oy = offsets[pin_idx]
            assert px == cx + ox
            assert py == cy + oy

    @pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
    def test_r90(self, symbol):
        """R90 applies (px, py) -> (-py, px)."""
        cx, cy = 160, 80
        for pin_idx in range(2):
            px, py = pin_position(symbol, pin_idx, cx, cy, rotation=90)
            offsets = _PIN_OFFSETS[symbol]
            ox, oy = offsets[pin_idx]
            assert px == cx + (-oy)
            assert py == cy + ox

    @pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
    def test_r180(self, symbol):
        """R180 applies (px, py) -> (-px, -py)."""
        cx, cy = 160, 80
        for pin_idx in range(2):
            px, py = pin_position(symbol, pin_idx, cx, cy, rotation=180)
            offsets = _PIN_OFFSETS[symbol]
            ox, oy = offsets[pin_idx]
            assert px == cx + (-ox)
            assert py == cy + (-oy)

    @pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
    def test_r270(self, symbol):
        """R270 applies (px, py) -> (py, -px)."""
        cx, cy = 160, 80
        for pin_idx in range(2):
            px, py = pin_position(symbol, pin_idx, cx, cy, rotation=270)
            offsets = _PIN_OFFSETS[symbol]
            ox, oy = offsets[pin_idx]
            assert px == cx + oy
            assert py == cy + (-ox)

    def test_unknown_symbol_defaults(self):
        """Unknown symbol uses default pin offsets."""
        px, py = pin_position("unknown", 0, 0, 0, rotation=0)
        # Default is [(0, 0), (0, 80)]
        assert (px, py) == (0, 0)
        px2, py2 = pin_position("unknown", 1, 0, 0, rotation=0)
        assert (px2, py2) == (0, 80)


class TestRotate:
    def test_identity(self):
        assert _rotate(10, 20, 0) == (10, 20)

    def test_90(self):
        assert _rotate(10, 20, 90) == (-20, 10)

    def test_180(self):
        assert _rotate(10, 20, 180) == (-10, -20)

    def test_270(self):
        assert _rotate(10, 20, 270) == (20, -10)

    def test_invalid_rotation(self):
        """Invalid rotation falls through to identity."""
        assert _rotate(10, 20, 45) == (10, 20)


class TestAscSchematicRender:
    def test_version_header(self):
        sch = AscSchematic()
        text = sch.render()
        assert text.startswith("Version 4\n")

    def test_sheet_dimensions(self):
        sch = AscSchematic(sheet_w=1200, sheet_h=900)
        text = sch.render()
        assert "SHEET 1 1200 900" in text

    def test_wire_rendering(self):
        sch = AscSchematic()
        sch.add_wire(80, 96, 176, 96)
        text = sch.render()
        assert "WIRE 80 96 176 96" in text

    def test_component_rendering(self):
        sch = AscSchematic()
        sch.add_component("res", "R1", "1k", 160, 80)
        text = sch.render()
        assert "SYMBOL res 160 80 R0" in text
        assert "SYMATTR InstName R1" in text
        assert "SYMATTR Value 1k" in text

    def test_rotated_component(self):
        sch = AscSchematic()
        sch.add_component("res", "R1", "1k", 160, 80, rotation=90)
        text = sch.render()
        assert "SYMBOL res 160 80 R90" in text

    def test_ground_flag(self):
        sch = AscSchematic()
        sch.add_ground(80, 176)
        text = sch.render()
        assert "FLAG 80 176 0" in text

    def test_net_label(self):
        sch = AscSchematic()
        sch.add_net_label("out", 176, 176)
        text = sch.render()
        assert "FLAG 176 176 out" in text

    def test_directive_rendering(self):
        sch = AscSchematic()
        sch.add_directive(".tran 10m", 80, 300)
        text = sch.render()
        assert "TEXT 80 300 Left 2 !.tran 10m" in text

    def test_chaining(self):
        sch = (
            AscSchematic()
            .add_component("res", "R1", "1k", 160, 80)
            .add_wire(80, 96, 176, 96)
            .add_ground(80, 176)
        )
        text = sch.render()
        assert "SYMBOL" in text
        assert "WIRE" in text
        assert "FLAG" in text


class TestAscTemplates:
    @pytest.mark.parametrize(
        "factory",
        [generate_rc_lowpass, generate_voltage_divider, generate_inverting_amp],
    )
    def test_template_returns_schematic(self, factory):
        sch = factory()
        assert isinstance(sch, AscSchematic)

    @pytest.mark.parametrize(
        "factory",
        [generate_rc_lowpass, generate_voltage_divider, generate_inverting_amp],
    )
    def test_template_nonempty(self, factory):
        text = factory().render()
        assert len(text) > 50
        assert "SYMBOL" in text

    @pytest.mark.parametrize(
        "factory",
        [generate_rc_lowpass, generate_voltage_divider, generate_inverting_amp],
    )
    def test_template_has_expected_components(self, factory):
        text = factory().render()
        # All templates should have at least a res and a voltage source
        assert "res" in text or "voltage" in text
