"""Bridge transport — wraps mcgpib's async BridgeConnection for pyvisa use.

When pyvisa-ar488 runs inside mcgpib, we don't open a new serial port.
Instead, we route through the existing BridgeConnection using
asyncio.run_coroutine_threadsafe() to cross the sync/async boundary.

pymeasure and pyvisa are synchronous. They run in a thread (via
asyncio.to_thread). This transport dispatches back to the event loop
where BridgeConnection lives, using its existing lock for serialization.

The key challenge: pyvisa calls write() and read() as separate operations,
but AR488 requires atomic command sequences (set address -> send -> read).
We use BridgeConnection's query_instrument() and write_instrument() which
handle this atomically under the bridge lock.
"""

import asyncio
import logging

from pyvisa_ar488.protocols import AR488Transport

logger = logging.getLogger(__name__)


class BridgeTransport(AR488Transport):
    """Synchronous wrapper around mcgpib's async BridgeConnection.

    Must be created with a reference to the BridgeConnection and the
    event loop it runs on. All operations use run_coroutine_threadsafe()
    to dispatch async calls from the synchronous pyvisa/pymeasure thread.

    Because pyvisa calls write() then read() as separate operations but
    AR488 needs atomic sequences, this transport buffers the last write
    command and combines it with the following read into a single
    query_instrument() call.
    """

    def __init__(self, bridge_connection, event_loop: asyncio.AbstractEventLoop):
        self._bridge = bridge_connection
        self._loop = event_loop
        self._current_address: int | None = None
        self._pending_command: str | None = None

    def _run_async(self, coro):
        """Run an async coroutine from a synchronous thread."""
        future = asyncio.run_coroutine_threadsafe(coro, self._loop)
        return future.result(timeout=30.0)

    def connect(self) -> None:
        # Bridge is already connected — managed by mcgpib lifecycle
        pass

    def disconnect(self) -> None:
        # Bridge lifecycle is managed by mcgpib, not by pyvisa
        pass

    def is_connected(self) -> bool:
        from mcgpib.models import ConnectionState

        return self._bridge.state == ConnectionState.connected

    def send_command(self, command: str) -> None:
        """Buffer the command — it will be sent atomically with the next read.

        If there's already a pending command, flush it as a write-only
        operation first.
        """
        if self._pending_command is not None:
            # Previous command was write-only (no read followed)
            self._flush_pending_write()
        self._pending_command = command

    def send_raw(self, command: str) -> None:
        """Send a raw ++ command directly."""
        self._run_async(self._bridge.raw_command(command))

    def read_response(self, timeout_ms: int = 10000) -> str:
        """Read response — combines with pending command for atomic query."""
        addr = self._current_address or 0
        if self._pending_command is not None:
            # Atomic query: set_address + send + read under one lock
            cmd = self._pending_command
            self._pending_command = None
            return self._run_async(
                self._bridge.query_instrument(addr, cmd)
            )
        else:
            # Read without a prior write — unusual but possible
            # Use raw_command to send ++read eoi
            result = self._run_async(self._bridge.raw_command("++read eoi"))
            return result or ""

    def set_address(self, address: int) -> None:
        # Flush any pending write before changing address
        if self._pending_command is not None and self._current_address != address:
            self._flush_pending_write()
        self._current_address = address

    def serial_poll(self, address: int) -> int:
        results = self._run_async(self._bridge.serial_poll(address=address))
        if results:
            return results[0].status_byte
        return 0

    def find_listeners(self) -> list[int]:
        bus_state = self._run_async(self._bridge.scan_bus(identify=False))
        return bus_state.listener_addresses

    def interface_clear(self) -> None:
        self._run_async(self._bridge.interface_clear())

    def _flush_pending_write(self) -> None:
        """Send the pending command as a write-only (no read)."""
        if self._pending_command is not None:
            addr = self._current_address or 0
            cmd = self._pending_command
            self._pending_command = None
            self._run_async(
                self._bridge.write_instrument(addr, cmd)
            )
