"""Tests for {{ module_name }} liquid handler interface.

These tests use the fake interface to validate the interface contract
without requiring actual hardware. They cover the full device lifecycle:
connect, initialize, aspirate, dispense, transfer, tips, shutdown, and disconnect.

Generated by: madsci new module --template module/liquid_handler
Template version: {{ template_version }}
"""

import pytest

from {{ module_name }}_fake_interface import {{ module_name | pascal_case }}FakeInterface


class Test{{ module_name | pascal_case }}FakeInterface:
    """Test cases for the {{ module_name }} fake liquid handler interface."""

    @pytest.fixture
    def interface(self) -> {{ module_name | pascal_case }}FakeInterface:
        """Create a test interface instance with low latency."""
        return {{ module_name | pascal_case }}FakeInterface(simulated_latency=0.01)

    def test_initial_state(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test that interface starts disconnected and uninitialized."""
        assert not interface.is_connected
        assert not interface.is_initialized
        assert not interface.has_tips
        assert interface.operation_count == 0
        assert interface.transfer_history == []

    def test_connect(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test connection."""
        interface.connect()
        assert interface.is_connected
        assert not interface.is_initialized

    def test_disconnect(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test disconnection."""
        interface.connect()
        interface.disconnect()
        assert not interface.is_connected
        assert not interface.is_initialized
        assert not interface.has_tips

    def test_initialize(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test device initialization."""
        interface.connect()
        interface.initialize()
        assert interface.is_initialized

    def test_initialize_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that initialization requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.initialize()

    def test_shutdown(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test device shutdown."""
        interface.connect()
        interface.initialize()
        interface.shutdown()
        assert not interface.is_initialized
        assert interface.is_connected  # Still connected after shutdown

    def test_shutdown_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that shutdown requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.shutdown()

    def test_aspirate(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test aspirate operation."""
        interface.connect()
        interface.initialize()
        interface.aspirate(100.0, well="A1")

        assert interface.operation_count == 1
        history = interface.transfer_history
        assert len(history) == 1
        assert history[0]["operation"] == "aspirate"
        assert history[0]["volume_ul"] == 100.0
        assert history[0]["well"] == "A1"

    def test_aspirate_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that aspirate requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.aspirate(100.0)

    def test_aspirate_requires_initialization(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that aspirate requires initialization."""
        interface.connect()
        with pytest.raises(RuntimeError, match="not initialized"):
            interface.aspirate(100.0)

    def test_dispense(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test dispense operation."""
        interface.connect()
        interface.initialize()
        interface.dispense(50.0, well="B1")

        assert interface.operation_count == 1
        history = interface.transfer_history
        assert len(history) == 1
        assert history[0]["operation"] == "dispense"
        assert history[0]["volume_ul"] == 50.0
        assert history[0]["well"] == "B1"

    def test_dispense_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that dispense requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.dispense(100.0)

    def test_dispense_requires_initialization(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that dispense requires initialization."""
        interface.connect()
        with pytest.raises(RuntimeError, match="not initialized"):
            interface.dispense(100.0)

    def test_transfer(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test transfer operation (aspirate + dispense)."""
        interface.connect()
        interface.initialize()
        interface.transfer(75.0, source_well="A1", dest_well="B1")

        # Transfer performs aspirate then dispense, so 2 operations
        assert interface.operation_count == 2
        history = interface.transfer_history
        assert len(history) == 2
        assert history[0]["operation"] == "aspirate"
        assert history[0]["well"] == "A1"
        assert history[0]["volume_ul"] == 75.0
        assert history[1]["operation"] == "dispense"
        assert history[1]["well"] == "B1"
        assert history[1]["volume_ul"] == 75.0

    def test_transfer_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that transfer requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.transfer(100.0, source_well="A1", dest_well="B1")

    def test_transfer_requires_initialization(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that transfer requires initialization."""
        interface.connect()
        with pytest.raises(RuntimeError, match="not initialized"):
            interface.transfer(100.0, source_well="A1", dest_well="B1")

    def test_pick_up_tips(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test picking up tips."""
        interface.connect()
        interface.initialize()
        assert not interface.has_tips

        interface.pick_up_tips()
        assert interface.has_tips
        assert interface.operation_count == 1

    def test_pick_up_tips_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that pick_up_tips requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.pick_up_tips()

    def test_pick_up_tips_requires_initialization(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that pick_up_tips requires initialization."""
        interface.connect()
        with pytest.raises(RuntimeError, match="not initialized"):
            interface.pick_up_tips()

    def test_drop_tips(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test dropping tips."""
        interface.connect()
        interface.initialize()
        interface.pick_up_tips()
        assert interface.has_tips

        interface.drop_tips()
        assert not interface.has_tips
        assert interface.operation_count == 2  # pick_up + drop

    def test_drop_tips_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that drop_tips requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.drop_tips()

    def test_drop_tips_requires_initialization(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that drop_tips requires initialization."""
        interface.connect()
        with pytest.raises(RuntimeError, match="not initialized"):
            interface.drop_tips()

    def test_get_status(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test status retrieval."""
        interface.connect()
        interface.initialize()
        status = interface.get_status()

        assert status["connected"] is True
        assert status["initialized"] is True
        assert status["has_tips"] is False
        assert status["current_volume_ul"] == 0.0
        assert status["status_code"] == 0

    def test_get_status_requires_connection(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that status retrieval requires connection."""
        with pytest.raises(RuntimeError, match="Not connected"):
            interface.get_status()

    def test_get_state(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test internal state retrieval."""
        interface.connect()
        interface.initialize()
        interface.aspirate(100.0, well="A1")

        state = interface.get_state()

        assert state["connected"] is True
        assert state["initialized"] is True
        assert state["has_tips"] is False
        assert state["current_volume_ul"] == 100.0
        assert state["operation_count"] == 1
        assert state["last_operation"] == "aspirate"
        assert "last_operation_time" in state

    def test_reset_state(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test state reset."""
        interface.connect()
        interface.initialize()
        interface.aspirate(100.0, well="A1")
        interface.reset_state()

        assert interface.operation_count == 0
        assert interface.transfer_history == []
        state = interface.get_state()
        assert "last_operation" not in state

    def test_operation_count(self, interface: {{ module_name | pascal_case }}FakeInterface) -> None:
        """Test that operation count increments correctly."""
        interface.connect()
        interface.initialize()

        interface.aspirate(100.0)
        assert interface.operation_count == 1

        interface.dispense(100.0)
        assert interface.operation_count == 2

        interface.pick_up_tips()
        assert interface.operation_count == 3

    def test_transfer_history(
        self, interface: {{ module_name | pascal_case }}FakeInterface
    ) -> None:
        """Test that transfer history is tracked correctly."""
        interface.connect()
        interface.initialize()

        interface.aspirate(100.0, well="A1")
        interface.dispense(100.0, well="B1")

        history = interface.transfer_history
        assert len(history) == 2
        assert history[0]["operation"] == "aspirate"
        assert history[0]["volume_ul"] == 100.0
        assert history[0]["well"] == "A1"
        assert history[1]["operation"] == "dispense"
        assert history[1]["volume_ul"] == 100.0
        assert history[1]["well"] == "B1"
