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

This module provides a simulated interface for testing without hardware.
It implements the same methods as the real interface but operates entirely
in memory, with configurable latency and full state tracking for testing.

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

import logging
import time
from typing import Any, Optional

from {{ module_name }}_types import {{ module_name | pascal_case }}InterfaceConfig

logger = logging.getLogger(__name__)


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

    This class simulates liquid handler behavior including full lifecycle
    management (connect, initialize, shutdown, disconnect) and liquid handling
    operations (aspirate, dispense, transfer, tips). It is designed for:
    - Development without physical hardware
    - Automated testing in CI/CD
    - Demo and training purposes
    - Debugging workflow logic

    Example:
        >>> interface = {{ module_name | pascal_case }}FakeInterface(simulated_latency=0.01)
        >>> interface.connect()
        >>> interface.initialize()
        >>> interface.pick_up_tips()
        >>> interface.aspirate(100.0, well="A1")
        >>> interface.dispense(100.0, well="B1")
        >>> interface.drop_tips()
        >>> interface.shutdown()
        >>> interface.disconnect()
    """

    def __init__(
        self,
        config: Optional[{{ module_name | pascal_case }}InterfaceConfig] = None,
        simulated_latency: float = 0.1,
        **kwargs: Any,
    ) -> None:
        """Initialize the fake interface.

        Args:
            config: Interface configuration. Uses defaults if None.
            simulated_latency: Simulated operation time in seconds.
            **kwargs: Additional configuration (ignored in fake mode).
        """
        self._config = config or {{ module_name | pascal_case }}InterfaceConfig(**kwargs)
        self._simulated_latency = simulated_latency
        self._connected = False
        self._initialized = False
        self._has_tips = False
        self._current_volume_ul = 0.0
        self._operation_count = 0
        self._state: dict[str, Any] = {}
        self._transfer_history: list[dict[str, Any]] = []

    @property
    def is_connected(self) -> bool:
        """Check if the interface is connected."""
        return self._connected

    @property
    def is_initialized(self) -> bool:
        """Check if the device has been initialized."""
        return self._initialized

    @property
    def has_tips(self) -> bool:
        """Check if the liquid handler currently has tips attached."""
        return self._has_tips

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

    @property
    def transfer_history(self) -> list[dict[str, Any]]:
        """Get the history of liquid handling operations."""
        return list(self._transfer_history)

    def connect(self) -> None:
        """Simulate establishing connection to the liquid handler."""
        logger.info("[FAKE] Connecting to simulated {{ module_name }} liquid handler...")
        time.sleep(self._simulated_latency)
        self._connected = True
        self._state["connected_at"] = time.time()
        logger.info("[FAKE] Connected to simulated {{ module_name }} liquid handler")

    def disconnect(self) -> None:
        """Simulate closing connection to the liquid handler."""
        if self._connected:
            logger.info(
                "[FAKE] Disconnecting from simulated {{ module_name }} liquid handler..."
            )
            self._connected = False
            self._initialized = False
            self._has_tips = False
            self._current_volume_ul = 0.0
            self._state["disconnected_at"] = time.time()
            logger.info(
                "[FAKE] Disconnected from simulated {{ module_name }} liquid handler"
            )

    def initialize(self) -> None:
        """Simulate device initialization.

        Raises:
            RuntimeError: If not connected.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")

        logger.info("[FAKE] Initializing simulated {{ module_name }} liquid handler...")
        time.sleep(self._simulated_latency)
        self._initialized = True
        self._state["initialized_at"] = time.time()
        logger.info("[FAKE] Simulated {{ module_name }} liquid handler initialized")

    def shutdown(self) -> None:
        """Simulate device shutdown.

        Raises:
            RuntimeError: If not connected.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")

        logger.info("[FAKE] Shutting down simulated {{ module_name }} liquid handler...")
        time.sleep(self._simulated_latency)
        self._initialized = False
        self._state["shutdown_at"] = time.time()
        logger.info("[FAKE] Simulated {{ module_name }} liquid handler shut down")

    def aspirate(
        self,
        volume_ul: float,
        well: Optional[str] = None,
        speed_ul_per_s: Optional[float] = None,
    ) -> None:
        """Simulate aspirating liquid.

        Args:
            volume_ul: Volume to aspirate in microliters.
            well: Well identifier to aspirate from. Optional.
            speed_ul_per_s: Aspiration speed in uL/s. Uses default if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        speed = speed_ul_per_s or self._config.default_speed_ul_per_s
        time.sleep(self._simulated_latency)

        self._current_volume_ul += volume_ul
        self._operation_count += 1
        self._state["last_operation"] = "aspirate"
        self._state["last_operation_time"] = time.time()

        record = {
            "operation": "aspirate",
            "volume_ul": volume_ul,
            "well": well,
            "speed_ul_per_s": speed,
            "operation_number": self._operation_count,
            "timestamp": time.time(),
        }
        self._transfer_history.append(record)

        logger.info(
            f"[FAKE] Aspirated {volume_ul} uL from {well or 'current position'}"
        )

    def dispense(
        self,
        volume_ul: float,
        well: Optional[str] = None,
        speed_ul_per_s: Optional[float] = None,
    ) -> None:
        """Simulate dispensing liquid.

        Args:
            volume_ul: Volume to dispense in microliters.
            well: Well identifier to dispense into. Optional.
            speed_ul_per_s: Dispense speed in uL/s. Uses default if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        speed = speed_ul_per_s or self._config.default_speed_ul_per_s
        time.sleep(self._simulated_latency)

        self._current_volume_ul = max(0.0, self._current_volume_ul - volume_ul)
        self._operation_count += 1
        self._state["last_operation"] = "dispense"
        self._state["last_operation_time"] = time.time()

        record = {
            "operation": "dispense",
            "volume_ul": volume_ul,
            "well": well,
            "speed_ul_per_s": speed,
            "operation_number": self._operation_count,
            "timestamp": time.time(),
        }
        self._transfer_history.append(record)

        logger.info(
            f"[FAKE] Dispensed {volume_ul} uL into {well or 'current position'}"
        )

    def transfer(
        self,
        volume_ul: float,
        source_well: str,
        dest_well: str,
        speed_ul_per_s: Optional[float] = None,
    ) -> None:
        """Simulate transferring liquid from one well to another.

        Performs an aspirate from source_well followed by a dispense
        into dest_well.

        Args:
            volume_ul: Volume to transfer in microliters.
            source_well: Well identifier to aspirate from.
            dest_well: Well identifier to dispense into.
            speed_ul_per_s: Pipetting speed in uL/s. Uses default if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        logger.info(
            f"[FAKE] Transferring {volume_ul} uL from {source_well} to {dest_well}"
        )
        self.aspirate(volume_ul, well=source_well, speed_ul_per_s=speed_ul_per_s)
        self.dispense(volume_ul, well=dest_well, speed_ul_per_s=speed_ul_per_s)

    def pick_up_tips(self, tip_rack: Optional[str] = None) -> None:
        """Simulate picking up disposable tips.

        Args:
            tip_rack: Tip rack identifier. Uses default rack if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        time.sleep(self._simulated_latency)
        self._has_tips = True
        self._operation_count += 1
        self._state["last_operation"] = "pick_up_tips"
        self._state["last_operation_time"] = time.time()

        record = {
            "operation": "pick_up_tips",
            "tip_rack": tip_rack,
            "operation_number": self._operation_count,
            "timestamp": time.time(),
        }
        self._transfer_history.append(record)

        logger.info(f"[FAKE] Picked up tips from {tip_rack or 'default rack'}")

    def drop_tips(self) -> None:
        """Simulate dropping current tips.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        time.sleep(self._simulated_latency)
        self._has_tips = False
        self._current_volume_ul = 0.0
        self._operation_count += 1
        self._state["last_operation"] = "drop_tips"
        self._state["last_operation_time"] = time.time()

        record = {
            "operation": "drop_tips",
            "operation_number": self._operation_count,
            "timestamp": time.time(),
        }
        self._transfer_history.append(record)

        logger.info("[FAKE] Dropped tips")

    def get_status(self) -> dict:
        """Get the simulated liquid handler status.

        Returns:
            dict: Simulated device status information.

        Raises:
            RuntimeError: If not connected.
        """
        if not self._connected:
            raise RuntimeError("Not connected. Call connect() first.")

        return {
            "connected": self._connected,
            "initialized": self._initialized,
            "has_tips": self._has_tips,
            "current_volume_ul": self._current_volume_ul,
            "status_code": 0,
            "operation_count": self._operation_count,
            "last_operation": self._state.get("last_operation"),
        }

    def get_state(self) -> dict[str, Any]:
        """Get the current internal state (useful for testing).

        Returns:
            Dictionary of internal state including connection status,
            initialization status, tip state, volume, operation count,
            and transfer history.
        """
        return {
            "connected": self._connected,
            "initialized": self._initialized,
            "has_tips": self._has_tips,
            "current_volume_ul": self._current_volume_ul,
            "operation_count": self._operation_count,
            "transfer_history_length": len(self._transfer_history),
            **self._state,
        }

    def reset_state(self) -> None:
        """Reset internal state (useful for testing).

        Clears operation count, transfer history, and internal state
        while preserving connection, initialization, and tip status.
        """
        self._operation_count = 0
        self._transfer_history = []
        self._state = {}
