"""Shared test fixtures for the RouterOS Python client library.

The central fixture is ``socket_pair`` which creates a connected pair of
in-process sockets so tests can simulate a real RouterOS device without
any network connectivity.
"""

from __future__ import annotations

import socket
import threading
from typing import Generator

import pytest

from routeros.proto import Reader, Writer
from routeros.sentence import Sentence

# ---------------------------------------------------------------------------
# MockServer – the "RouterOS device" side of the socket pair
# ---------------------------------------------------------------------------


class MockServer:
    """Minimal server side of the test socket pair.

    Wraps a :class:`~routeros_python.proto.Reader` and
    :class:`~routeros_python.proto.Writer` to let tests easily exchange
    sentences with the client under test.
    """

    def __init__(self, sock: socket.socket) -> None:
        self._sock = sock
        self._reader = Reader(sock)
        self._writer = Writer(sock)

    def read_sentence(self) -> Sentence:
        """Read and return the next sentence sent by the client."""
        return self._reader.read_sentence()

    def write_sentence(self, *words: str) -> None:
        """Send *words* as a sentence to the client."""
        self._writer.write_sentence(list(words))

    def close(self) -> None:
        self._sock.close()


# ---------------------------------------------------------------------------
# Pytest fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def socket_pair() -> Generator[tuple[socket.socket, MockServer], None, None]:
    """Yield ``(client_sock, mock_server)`` using ``socket.socketpair()``.

    The first element is a raw :class:`socket.socket` to pass to
    :class:`~routeros_python.client.Client`.
    The second element is a :class:`MockServer` for the test to drive.
    Both sockets are closed after the test.
    """
    client_sock, server_sock = socket.socketpair()
    server = MockServer(server_sock)
    try:
        yield client_sock, server
    finally:
        try:
            client_sock.close()
        except OSError:
            pass
        try:
            server.close()
        except OSError:
            pass


# ---------------------------------------------------------------------------
# Helpers used by individual test modules
# ---------------------------------------------------------------------------


def run_server(fn, *args, **kwargs) -> threading.Thread:
    """Run *fn* in a daemon thread and return the thread."""
    t = threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True)
    t.start()
    return t
