import logging
from contextlib import contextmanager
from pathlib import Path
from random import randint
from subprocess import PIPE, STDOUT, Popen
from time import sleep
from typing import Generator, Protocol

import pytest
from libretime_shared.logging import setup_logger

from libretime_playout.liquidsoap.client import LiquidsoapClient, LiquidsoapConnection

logger = logging.getLogger(__name__)

setup_logger("debug")


LIQ_SCRIPT = """
set("log.file", false)
{settings}

var1 = interactive.string("var1", "default")

output.dummy(blank(id="safe_blank"))
"""

LIQ_TELNET_SETTINGS = """
set("server.telnet", true)
set("server.telnet.port", {telnet_port})
"""

LIQ_SOCKET_SETTINGS = """
set("server.socket", true)
set("server.socket.path", "{socket_path}")
"""


class LiquidsoapManager(Protocol):
    def generate_entrypoint(self) -> str:
        pass

    def wait_start(self, process: Popen) -> None:
        pass

    def make_connection(self) -> LiquidsoapConnection:
        pass

    def make_client(self) -> LiquidsoapClient:
        pass


class LiquidsoapManagerTelnet:
    def __init__(self) -> None:
        self.telnet_port = randint(32768, 65535)

    def generate_entrypoint(self) -> str:
        liq_settings = LIQ_TELNET_SETTINGS.format(telnet_port=self.telnet_port)
        liq_script = LIQ_SCRIPT.format(settings=liq_settings.strip())
        return liq_script

    # pylint: disable=unused-argument
    def wait_start(self, process: Popen) -> None:
        sleep(2)

    def make_connection(self) -> LiquidsoapConnection:
        return LiquidsoapConnection(host="localhost", port=self.telnet_port)

    def make_client(self) -> LiquidsoapClient:
        return LiquidsoapClient(host="localhost", port=self.telnet_port)


class LiquidsoapManagerSocket:
    def __init__(self, tmp_path: Path) -> None:
        self.socket_path = tmp_path / "main.sock"

    def generate_entrypoint(self) -> str:
        liq_settings = LIQ_SOCKET_SETTINGS.format(socket_path=self.socket_path)
        liq_script = LIQ_SCRIPT.format(settings=liq_settings.strip())
        return liq_script

    def wait_start(self, process: Popen):
        while process.poll() is None and not self.socket_path.is_socket():
            sleep(0.1)

    def make_connection(self) -> LiquidsoapConnection:
        return LiquidsoapConnection(path=self.socket_path)

    def make_client(self) -> LiquidsoapClient:
        return LiquidsoapClient(path=self.socket_path)


@contextmanager
def run_liq_server(
    kind: str,
    tmp_path: Path,
) -> Generator[LiquidsoapManager, None, None]:
    entrypoint = tmp_path / "main.liq"

    manager: LiquidsoapManager
    if kind == "telnet":
        manager = LiquidsoapManagerTelnet()
    elif kind == "socket":
        manager = LiquidsoapManagerSocket(tmp_path)

    liq_script = manager.generate_entrypoint()
    logger.debug(liq_script)
    entrypoint.write_text(liq_script)

    # The --verbose flag seem to hang when testing in CI
    with Popen(
        ("liquidsoap", "--debug", str(entrypoint)),
        stdout=PIPE,
        stderr=STDOUT,
        text=True,
    ) as process:
        manager.wait_start(process)

        if process.poll() is not None:
            pytest.fail(process.stdout.read())

        yield manager

        process.terminate()


@pytest.fixture(
    name="liq_conn",
    scope="session",
    params=["telnet", "socket"],
)
def liq_conn_fixture(request, tmp_path_factory):
    tmp_path: Path = tmp_path_factory.mktemp(__name__)

    with run_liq_server(request.param, tmp_path) as manager:
        conn = manager.make_connection()
        with conn:
            yield conn


@pytest.fixture(
    name="liq_client",
    scope="session",
    params=["telnet", "socket"],
)
def liq_client_fixture(request, tmp_path_factory):
    tmp_path: Path = tmp_path_factory.mktemp(__name__)

    with run_liq_server(request.param, tmp_path) as manager:
        yield manager.make_client()