"""Android USB-serial transport backed by the Kotlin ``UsbSerialBridge``.

Calls into Java land through Chaquopy; this module only loads on
Android. URL format: ``usb:VID:PID`` or ``usb:VID:PID:serial`` (with an
optional ``@baud`` suffix).
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import List, Tuple

from ledgrab.utils import get_logger
from ledgrab.utils.platform import is_android

from .serial_transport import SerialPortInfo

logger = get_logger(__name__)


def _bridge():
    """Return the singleton Kotlin ``UsbSerialBridge`` instance, or raise.

    The bridge exposes static methods ``listDevices()``, ``open(...)``,
    ``write(...)``, ``close(...)``, etc. — see ``UsbSerialBridge.kt``.
    """
    if not is_android():
        raise RuntimeError("AndroidSerialTransport is only usable on Android")
    try:
        from java import jclass  # type: ignore[import-not-found]
    except ImportError as e:
        raise RuntimeError("Chaquopy java interop not available") from e

    return jclass("com.ledgrab.android.UsbSerialBridge").INSTANCE


@dataclass(frozen=True)
class _UsbAddress:
    vendor_id: int
    product_id: int
    serial: str | None

    @classmethod
    def parse(cls, device: str) -> "_UsbAddress":
        if not device.startswith("usb:"):
            raise ValueError(f"Not a USB device URL: {device!r}")
        body = device[len("usb:") :]
        parts = body.split(":")
        if len(parts) < 2:
            raise ValueError(
                f"USB URL must be 'usb:VID:PID' or 'usb:VID:PID:serial' (got {device!r})"
            )
        try:
            vid = int(parts[0], 16)
            pid = int(parts[1], 16)
        except ValueError as e:
            raise ValueError(f"VID/PID must be hex: {device!r}") from e
        serial = parts[2] if len(parts) >= 3 and parts[2] else None
        return cls(vid, pid, serial)


def _format_url(vid: int, pid: int, serial: str | None) -> str:
    base = f"usb:{vid:04x}:{pid:04x}"
    return f"{base}:{serial}" if serial else base


def list_android_usb_devices() -> List[SerialPortInfo]:
    """Return USB-serial devices currently attached to the Android host."""
    try:
        bridge = _bridge()
    except Exception as e:
        logger.debug("UsbSerialBridge unavailable: %s", e)
        return []

    out: List[SerialPortInfo] = []
    try:
        # listDevices() returns a Java List<String> of "vid:pid:serial:description"
        # tuples — split here to keep the Kotlin DTO trivial.
        for entry in bridge.listDevices():
            entry_s = str(entry)
            parts = entry_s.split("|", 3)
            if len(parts) < 4:
                continue
            vid_str, pid_str, serial, description = parts
            try:
                vid = int(vid_str, 16)
                pid = int(pid_str, 16)
            except ValueError:
                continue
            url = _format_url(vid, pid, serial or None)
            out.append(SerialPortInfo(device=url, description=description or url))
    except Exception as e:
        logger.warning("UsbSerialBridge.listDevices failed: %s", e)
    return out


class AndroidSerialTransport:
    """Serial transport that pipes writes through the Kotlin USB bridge."""

    def __init__(self, device: str, baud_rate: int) -> None:
        self._url = device
        self._addr = _UsbAddress.parse(device)
        self._baud_rate = baud_rate
        self._handle: int | None = None  # opaque token from the bridge

    @property
    def is_open(self) -> bool:
        return self._handle is not None

    def open(self) -> None:
        bridge = _bridge()
        # open() returns a non-negative int handle on success, or -1 if the
        # device wasn't found / permission was denied.
        handle = int(
            bridge.open(
                self._addr.vendor_id,
                self._addr.product_id,
                self._addr.serial or "",
                self._baud_rate,
            )
        )
        if handle < 0:
            raise RuntimeError(
                f"Failed to open USB serial device {self._url} "
                f"(handle={handle}; user may have denied permission)"
            )
        self._handle = handle
        logger.info("Android USB serial opened: %s @ %d baud", self._url, self._baud_rate)

    def write(self, data: bytes) -> None:
        if self._handle is None:
            raise RuntimeError(f"USB serial {self._url} is not open")
        bridge = _bridge()
        # Chaquopy auto-marshals bytes → Java byte[].
        bridge.write(self._handle, data)

    def flush(self) -> None:
        # USB endpoints are unbuffered from this side — nothing to flush.
        return

    def close(self) -> None:
        if self._handle is None:
            return
        try:
            _bridge().close(self._handle)
        except Exception as e:
            logger.warning("Error closing USB serial %s: %s", self._url, e)
        finally:
            self._handle = None


__all__ = [
    "AndroidSerialTransport",
    "list_android_usb_devices",
]


def _used() -> Tuple[type, ...]:  # pragma: no cover — re-export marker
    return (AndroidSerialTransport,)
