import os
import signal
import subprocess
import sys

import pytest

from tests.consts import ssl_path, tests_path
from tests.paho_test import create_server_socket, create_server_socket_ssl, ssl

clients_path = tests_path / "lib" / "clients"


def _yield_server(monkeypatch, sockport):
    sock, port = sockport
    monkeypatch.setenv("PAHO_SERVER_PORT", str(port))
    try:
        yield sock
    finally:
        sock.close()


@pytest.fixture()
def server_socket(monkeypatch):
    yield from _yield_server(monkeypatch, create_server_socket())


@pytest.fixture()
def ssl_server_socket(monkeypatch):
    if ssl is None:
        pytest.skip("no ssl module")
    yield from _yield_server(monkeypatch, create_server_socket_ssl())


@pytest.fixture()
def alpn_ssl_server_socket(monkeypatch):
    if ssl is None:
        pytest.skip("no ssl module")
    if not getattr(ssl, "HAS_ALPN", False):
        pytest.skip("ALPN not supported in this version of Python")
    yield from _yield_server(monkeypatch, create_server_socket_ssl(alpn_protocols=["paho-test-protocol"]))


def stop_process(proc: subprocess.Popen) -> None:
    if sys.platform == "win32":
        proc.send_signal(signal.CTRL_C_EVENT)
    else:
        proc.send_signal(signal.SIGINT)
    try:
        proc.wait(5)
    except subprocess.TimeoutExpired:
        proc.terminate()


@pytest.fixture()
def start_client(request: pytest.FixtureRequest):
    def starter(name: str, expected_returncode: int = 0) -> None:
        client_path = clients_path / name
        if not client_path.exists():
            raise FileNotFoundError(client_path)
        env = dict(
            os.environ,
            PAHO_SSL_PATH=str(ssl_path),
            PYTHONPATH=f"{tests_path}{os.pathsep}{os.environ.get('PYTHONPATH', '')}",
        )
        assert 'PAHO_SERVER_PORT' in env, "PAHO_SERVER_PORT must be set in the environment when starting a client"
        proc = subprocess.Popen([  # noqa: S603
            sys.executable,
            str(client_path),
        ], env=env)

        def fin():
            stop_process(proc)
            if proc.returncode != expected_returncode:
                raise RuntimeError(f"Client {name} exited with code {proc.returncode}, expected {expected_returncode}")

        request.addfinalizer(fin)
        return proc

    return starter
