from __future__ import annotations

import abc
import logging
import os
import random
import tempfile
import threading
from contextlib import suppress
from pathlib import Path

from streamlink.compat import is_win32


try:
    from ctypes import byref, c_ulong, c_void_p, cast, windll  # type: ignore[attr-defined]
except ImportError:
    pass


log = logging.getLogger(__name__)

_lock = threading.Lock()
_id = 0


class NamedPipeBase(abc.ABC):
    path: Path

    def __init__(self):
        global _id  # noqa: PLW0603
        with _lock:
            _id += 1
            self.name = f"streamlinkpipe-{os.getpid()}-{_id}-{random.randint(0, 9999)}"
        log.info(f"Creating pipe {self.name}")
        self._create()

    @abc.abstractmethod
    def _create(self) -> None:
        raise NotImplementedError

    @abc.abstractmethod
    def open(self) -> None:
        raise NotImplementedError

    @abc.abstractmethod
    def write(self, data) -> int:
        raise NotImplementedError

    @abc.abstractmethod
    def close(self) -> None:
        raise NotImplementedError


class NamedPipePosix(NamedPipeBase):
    mode = "wb"
    permissions = 0o660
    fifo = None

    def _create(self):
        self.path = Path(tempfile.gettempdir(), self.name)
        os.mkfifo(self.path, self.permissions)

    def open(self):
        self.fifo = self.path.open(self.mode)

    def write(self, data):
        return self.fifo.write(data)

    def close(self):
        try:
            if self.fifo is not None:
                self.fifo.close()
        except OSError:
            raise
        finally:
            with suppress(OSError):
                self.path.unlink()
            self.fifo = None


class NamedPipeWindows(NamedPipeBase):
    bufsize = 8192
    pipe = None

    PIPE_ACCESS_OUTBOUND = 0x00000002
    PIPE_TYPE_BYTE = 0x00000000
    PIPE_READMODE_BYTE = 0x00000000
    PIPE_WAIT = 0x00000000
    PIPE_UNLIMITED_INSTANCES = 255
    INVALID_HANDLE_VALUE = -1

    @staticmethod
    def _get_last_error():
        error_code = windll.kernel32.GetLastError()
        raise OSError(f"Named pipe error code 0x{error_code:08X}")

    def _create(self):
        self.path = Path("\\\\.\\pipe", self.name)
        self.pipe = windll.kernel32.CreateNamedPipeW(
            str(self.path),
            self.PIPE_ACCESS_OUTBOUND,
            self.PIPE_TYPE_BYTE | self.PIPE_READMODE_BYTE | self.PIPE_WAIT,
            self.PIPE_UNLIMITED_INSTANCES,
            self.bufsize,
            self.bufsize,
            0,
            None,
        )
        if self.pipe == self.INVALID_HANDLE_VALUE:
            self._get_last_error()

    def open(self):
        windll.kernel32.ConnectNamedPipe(self.pipe, None)

    def write(self, data):
        written = c_ulong(0)
        windll.kernel32.WriteFile(
            self.pipe,
            cast(data, c_void_p),
            len(data),
            byref(written),
            None,
        )
        return written.value

    def close(self):
        try:
            if self.pipe is not None:
                windll.kernel32.DisconnectNamedPipe(self.pipe)
                windll.kernel32.CloseHandle(self.pipe)
        except OSError:
            raise
        finally:
            self.pipe = None


NamedPipe: type[NamedPipeBase]
if not is_win32:
    NamedPipe = NamedPipePosix
else:
    NamedPipe = NamedPipeWindows
