"""Typed one-shot Linux inotify file-write subscription."""

from __future__ import annotations

import ctypes
import os
import select
from collections.abc import Callable
from dataclasses import dataclass
from typing import Final

_IN_CLOSE_WRITE: Final = 0x00000008
_IN_MOVED_TO: Final = 0x00000080
_HEADER: Final = 16


class InotifyError(RuntimeError):
    """The selected file event was not observed exactly once."""


@dataclass(frozen=True, slots=True)
class InotifyWatch:
    """Armed directory descriptor and watch identity."""

    descriptor: int
    watch: int

    @classmethod
    def arm(cls, directory: bytes) -> InotifyWatch:
        """Arm before triggering the operation under observation."""
        library = _Libc()
        descriptor = library.init(os.O_CLOEXEC | os.O_NONBLOCK)
        if descriptor < 0:
            raise InotifyError("inotify init")
        watch = library.watch(descriptor, directory, _IN_CLOSE_WRITE | _IN_MOVED_TO)
        if watch < 0:
            os.close(descriptor)
            raise InotifyError("inotify watch")
        return cls(descriptor, watch)

    def wait(self, name: str, timeout_seconds: int) -> None:
        """Wait for one exact close-write or moved-to event."""
        try:
            readable, _, _ = select.select((self.descriptor,), (), (), timeout_seconds)
            if not readable:
                raise InotifyError("event timeout")
            payload = os.read(self.descriptor, 65_536)
            if not _matches(payload, self.watch, name):
                raise InotifyError("wrong event")
        finally:
            os.close(self.descriptor)


class _Libc:
    def __init__(self) -> None:
        library = ctypes.CDLL("libc.so.6", use_errno=True)
        self.init: Callable[[int], int] = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)(
            ("inotify_init1", library)
        )
        self.watch: Callable[[int, bytes, int], int] = ctypes.CFUNCTYPE(
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_char_p,
            ctypes.c_uint32,
        )(("inotify_add_watch", library))


def _matches(payload: bytes, watch: int, expected_name: str) -> bool:
    offset = 0
    while offset + _HEADER <= len(payload):
        watched = int.from_bytes(payload[offset : offset + 4], "little", signed=True)
        mask = int.from_bytes(payload[offset + 4 : offset + 8], "little")
        size = int.from_bytes(payload[offset + 12 : offset + 16], "little")
        end = offset + _HEADER + size
        if end > len(payload):
            return False
        name = payload[offset + _HEADER : end].rstrip(b"\0").decode()
        if watched == watch and name == expected_name and mask & (_IN_CLOSE_WRITE | _IN_MOVED_TO):
            return True
        offset = end
    return False
