"""Asynchronous serial port support."""

from __future__ import annotations

import asyncio
from collections.abc import Callable
import logging
from typing import Any, Generic, TypeVar, cast

from .common import BaseSerialTransport, Parity, StopBits, get_uri_handler

LOGGER = logging.getLogger(__name__)

_T = TypeVar("_T", bound=asyncio.WriteTransport)


class SerialStreamWriter(asyncio.StreamWriter, Generic[_T]):
    """StreamWriter with properly typed transport."""

    @property
    def transport(self) -> _T:
        """Return the underlying transport."""
        return cast(_T, super().transport)


async def create_serial_connection(
    loop: asyncio.AbstractEventLoop,
    protocol_factory: Callable[[], asyncio.Protocol],
    url: str | None,
    baudrate: int,
    parity: Parity = Parity.NONE,
    stopbits: StopBits = StopBits.ONE,
    xonxoff: bool = False,
    rtscts: bool = False,
    exclusive: bool = True,
    *,
    transport_cls: type[BaseSerialTransport] | None = None,
    **kwargs: Any,
) -> tuple[BaseSerialTransport, asyncio.Protocol]:
    """Create a serial port connection with asyncio."""
    if transport_cls is None:
        if url is None:
            raise ValueError("One of `url` or `transport_cls` must be provided.")

        handler = await asyncio.get_running_loop().run_in_executor(
            None, get_uri_handler, url
        )
        transport_cls = handler.async_transport_cls

    assert transport_cls is not None
    protocol = protocol_factory()
    transport = transport_cls(loop=loop, protocol=protocol)

    await transport.connect(
        path=url,
        baudrate=baudrate,
        parity=parity,
        stopbits=stopbits,
        xonxoff=xonxoff,
        rtscts=rtscts,
        exclusive=exclusive,
        **kwargs,
    )

    return transport, protocol


async def open_serial_connection(
    *args: Any, **kwargs: Any
) -> tuple[asyncio.StreamReader, SerialStreamWriter[BaseSerialTransport]]:
    """Open a serial port connection using StreamReader and StreamWriter."""
    loop = asyncio.get_running_loop()

    reader = asyncio.StreamReader(loop=loop)
    protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
    transport, _ = await create_serial_connection(
        loop, lambda: protocol, *args, **kwargs
    )
    writer: SerialStreamWriter[BaseSerialTransport] = SerialStreamWriter(
        transport, protocol, reader, loop
    )

    return reader, writer
