"""GPIB INSTR session for AR488 backend.

One AR488InstrSession per open pyvisa resource. Translates pyvisa read/write
calls into AR488 ++ command sequences via the transport layer.
"""

import logging
from typing import TYPE_CHECKING, Any

from pyvisa import constants, rname
from pyvisa.constants import ResourceAttribute, StatusCode

if TYPE_CHECKING:
    from pyvisa_ar488.protocols import AR488Transport

logger = logging.getLogger(__name__)


class AR488InstrSession:
    """Session for a single GPIB instrument accessed through AR488.

    Holds the GPIB primary address and a reference to the shared transport.
    Multiple sessions share the same transport (one per bridge), but each
    targets a different GPIB address.
    """

    def __init__(
        self,
        transport: "AR488Transport",
        resource_name: str,
        primary_address: int,
        secondary_address: int | None = None,
        open_timeout: int = 0,
    ):
        self._transport = transport
        self.resource_name = resource_name
        self.primary_address = primary_address
        self.secondary_address = secondary_address
        self._timeout_ms: int = 10000  # default 10s
        self._send_end = True  # assert EOI on last byte

        logger.debug(
            "Opened GPIB session: address %d (resource: %s)",
            primary_address,
            resource_name,
        )

    def close(self) -> StatusCode:
        logger.debug("Closed GPIB session: address %d", self.primary_address)
        return StatusCode.success

    def write(self, data: bytes) -> tuple[int, StatusCode]:
        """Send data to the instrument.

        Sequence: ++addr <address>, then send the data as a line.
        """
        command = data.decode("ascii", errors="replace").rstrip("\r\n")
        self._transport.set_address(self.primary_address)
        self._transport.send_command(command)
        return len(data), StatusCode.success

    def read(self, count: int) -> tuple[bytes, StatusCode]:
        """Read response from the instrument.

        Sends ++read eoi to trigger the read, then returns the response.
        """
        response = self._transport.read_response(timeout_ms=self._timeout_ms)
        data = (response + "\n").encode("ascii")
        if len(data) > count:
            data = data[:count]
        return data, StatusCode.success

    def read_stb(self) -> tuple[int, StatusCode]:
        """Serial poll this instrument."""
        stb = self._transport.serial_poll(self.primary_address)
        return stb, StatusCode.success

    def clear(self) -> StatusCode:
        """Send Selected Device Clear to this instrument."""
        self._transport.set_address(self.primary_address)
        self._transport.send_raw("++clr")
        return StatusCode.success

    def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode:
        """Send Group Execute Trigger to this instrument."""
        self._transport.set_address(self.primary_address)
        self._transport.send_raw("++trg")
        return StatusCode.success

    def get_attribute(self, attribute: ResourceAttribute) -> tuple[Any, StatusCode]:
        """Get a VISA attribute for this session."""
        if attribute == ResourceAttribute.timeout_value:
            return self._timeout_ms, StatusCode.success
        if attribute == ResourceAttribute.send_end_enabled:
            return self._send_end, StatusCode.success
        if attribute == ResourceAttribute.gpib_primary_address:
            return self.primary_address, StatusCode.success
        if attribute == ResourceAttribute.gpib_secondary_address:
            if self.secondary_address is not None:
                return self.secondary_address, StatusCode.success
            return constants.VI_NO_SEC_ADDR, StatusCode.success
        if attribute == ResourceAttribute.interface_type:
            return constants.InterfaceType.gpib, StatusCode.success
        if attribute == ResourceAttribute.interface_number:
            return 0, StatusCode.success
        if attribute == ResourceAttribute.resource_name:
            return self.resource_name, StatusCode.success
        if attribute == ResourceAttribute.resource_class:
            return "INSTR", StatusCode.success
        if attribute == ResourceAttribute.termchar:
            return ord("\n"), StatusCode.success
        if attribute == ResourceAttribute.termchar_enabled:
            return True, StatusCode.success

        logger.debug("Unhandled get_attribute: %s", attribute)
        return 0, StatusCode.error_nonsupported_attribute

    def set_attribute(
        self, attribute: ResourceAttribute, value: Any
    ) -> StatusCode:
        """Set a VISA attribute for this session."""
        if attribute == ResourceAttribute.timeout_value:
            self._timeout_ms = int(value)
            return StatusCode.success
        if attribute == ResourceAttribute.send_end_enabled:
            self._send_end = bool(value)
            return StatusCode.success
        if attribute == ResourceAttribute.termchar:
            return StatusCode.success  # we always use \n
        if attribute == ResourceAttribute.termchar_enabled:
            return StatusCode.success

        logger.debug("Unhandled set_attribute: %s = %s", attribute, value)
        return StatusCode.error_nonsupported_attribute

    @staticmethod
    def parse_resource(resource_name: str) -> dict:
        """Parse a VISA resource name into components."""
        parsed = rname.parse_resource_name(resource_name)
        return {
            "interface_type": "GPIB",
            "board": getattr(parsed, "board", "0"),
            "primary_address": getattr(parsed, "primary_address", None),
            "secondary_address": getattr(parsed, "secondary_address", None),
            "resource_class": getattr(parsed, "resource_class", "INSTR"),
        }
