"""Fake Modbus interface for {{ interface_name }}.

Simulated Modbus device for testing without hardware or a PLC.

Generated by: madsci new comm --template comm/modbus
Template version: {{ template_version }}
"""

import logging
import time
from typing import Any

logger = logging.getLogger(__name__)


class {{ interface_name | pascal_case }}FakeInterface:
    """Fake Modbus interface for testing {{ interface_name }} without hardware.

    Simulates Modbus register and coil storage with configurable initial state.

    Example:
        >>> interface = {{ interface_name | pascal_case }}FakeInterface()
        >>> interface.connect()
        >>> interface.write_register(address=0, value=1234)
        >>> values = interface.read_holding_registers(address=0, count=1)
        >>> assert values == [1234]
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 502,
        unit_id: int = 1,
        protocol: str = "tcp",
        timeout: float = 5.0,
        simulated_latency: float = 0.01,
        register_count: int = 256,
        coil_count: int = 256,
        **kwargs: Any,
    ) -> None:
        """Initialize the fake Modbus interface.

        Args:
            host: Simulated host address.
            port: Simulated port number.
            unit_id: Simulated unit ID.
            protocol: Protocol type, either "tcp" or "rtu".
            timeout: Simulated timeout.
            simulated_latency: Artificial delay for each operation in seconds.
            register_count: Number of simulated registers to allocate.
            coil_count: Number of simulated coils to allocate.
            **kwargs: Additional configuration (ignored).
        """
        self._host = host
        self._port = port
        self._unit_id = unit_id
        self._protocol = protocol.lower()
        self._timeout = timeout
        self._simulated_latency = simulated_latency
        self._is_connected = False
        self._holding_registers: list[int] = [0] * register_count
        self._input_registers: list[int] = [0] * register_count
        self._coils: list[bool] = [False] * coil_count
        self._operation_count = 0

        if self._protocol not in ("tcp", "rtu"):
            raise ValueError(f"Unsupported protocol: {self._protocol}. Use 'tcp' or 'rtu'.")

    @property
    def is_connected(self) -> bool:
        """Check if the Modbus client is connected."""
        return self._is_connected

    @property
    def operation_count(self) -> int:
        """Get the total number of register/coil operations performed."""
        return self._operation_count

    def connect(self) -> None:
        """Simulate establishing a Modbus connection."""
        time.sleep(self._simulated_latency)
        self._is_connected = True
        logger.info(
            f"[FAKE] Connected to Modbus device at {self._host}:{self._port} "
            f"(unit {self._unit_id}, {self._protocol.upper()})"
        )

    def disconnect(self) -> None:
        """Simulate closing the Modbus connection."""
        if self._is_connected:
            self._is_connected = False
            logger.info("[FAKE] Disconnected from Modbus device")

    def read_holding_registers(self, address: int, count: int = 1) -> list[int]:
        """Simulate reading holding registers.

        Args:
            address: Starting register address.
            count: Number of registers to read.

        Returns:
            List of register values.

        Raises:
            RuntimeError: If not connected.
            IndexError: If address is out of range.
        """
        if not self._is_connected:
            raise RuntimeError("Not connected. Call connect() first.")
        time.sleep(self._simulated_latency)
        self._operation_count += 1
        return list(self._holding_registers[address : address + count])

    def read_input_registers(self, address: int, count: int = 1) -> list[int]:
        """Simulate reading input registers.

        Args:
            address: Starting register address.
            count: Number of registers to read.

        Returns:
            List of register values.

        Raises:
            RuntimeError: If not connected.
            IndexError: If address is out of range.
        """
        if not self._is_connected:
            raise RuntimeError("Not connected. Call connect() first.")
        time.sleep(self._simulated_latency)
        self._operation_count += 1
        return list(self._input_registers[address : address + count])

    def write_register(self, address: int, value: int) -> None:
        """Simulate writing a single holding register.

        Args:
            address: Register address to write.
            value: Value to write.

        Raises:
            RuntimeError: If not connected.
            IndexError: If address is out of range.
        """
        if not self._is_connected:
            raise RuntimeError("Not connected. Call connect() first.")
        time.sleep(self._simulated_latency)
        self._operation_count += 1
        self._holding_registers[address] = value

    def write_registers(self, address: int, values: list[int]) -> None:
        """Simulate writing multiple holding registers.

        Args:
            address: Starting register address.
            values: List of values to write.

        Raises:
            RuntimeError: If not connected.
            IndexError: If address range is out of bounds.
        """
        if not self._is_connected:
            raise RuntimeError("Not connected. Call connect() first.")
        time.sleep(self._simulated_latency)
        self._operation_count += 1
        for i, val in enumerate(values):
            self._holding_registers[address + i] = val

    def read_coils(self, address: int, count: int = 1) -> list[bool]:
        """Simulate reading coils.

        Args:
            address: Starting coil address.
            count: Number of coils to read.

        Returns:
            List of coil states.

        Raises:
            RuntimeError: If not connected.
            IndexError: If address is out of range.
        """
        if not self._is_connected:
            raise RuntimeError("Not connected. Call connect() first.")
        time.sleep(self._simulated_latency)
        self._operation_count += 1
        return list(self._coils[address : address + count])

    def write_coil(self, address: int, value: bool) -> None:
        """Simulate writing a single coil.

        Args:
            address: Coil address to write.
            value: Coil state (True/False).

        Raises:
            RuntimeError: If not connected.
            IndexError: If address is out of range.
        """
        if not self._is_connected:
            raise RuntimeError("Not connected. Call connect() first.")
        time.sleep(self._simulated_latency)
        self._operation_count += 1
        self._coils[address] = value

    def set_input_register(self, address: int, value: int) -> None:
        """Set an input register value for testing (input registers are read-only in Modbus).

        Args:
            address: Register address to set.
            value: Value to set.
        """
        self._input_registers[address] = value

    def reset(self) -> None:
        """Reset all registers, coils, and counters to initial state."""
        self._holding_registers = [0] * len(self._holding_registers)
        self._input_registers = [0] * len(self._input_registers)
        self._coils = [False] * len(self._coils)
        self._operation_count = 0
