from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
import os


class InstanceLockError(RuntimeError):
    pass


@dataclass(slots=True)
class InstanceLock:
    lock_path: Path
    enabled: bool = True
    _handle: object | None = field(default=None, init=False, repr=False)

    def __enter__(self) -> "InstanceLock":
        if not self.enabled:
            return self

        self.lock_path.parent.mkdir(parents=True, exist_ok=True)
        self._handle = self.lock_path.open("a+", encoding="utf-8")
        self._handle.seek(0)

        if os.name == "nt":  # pragma: no cover - windows-only branch
            import msvcrt

            try:
                msvcrt.locking(self._handle.fileno(), msvcrt.LK_NBLCK, 1)
            except OSError as exc:
                raise InstanceLockError(
                    f"Another obsidian-sync process is active (lock: {self.lock_path})."
                ) from exc
        else:
            import fcntl

            try:
                fcntl.flock(self._handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
            except OSError as exc:
                raise InstanceLockError(
                    f"Another obsidian-sync process is active (lock: {self.lock_path})."
                ) from exc

        self._handle.truncate(0)
        self._handle.write(str(os.getpid()))
        self._handle.flush()
        return self

    def __exit__(self, exc_type, exc, tb) -> None:
        if self._handle is None:
            return

        if os.name == "nt":  # pragma: no cover - windows-only branch
            import msvcrt

            try:
                self._handle.seek(0)
                msvcrt.locking(self._handle.fileno(), msvcrt.LK_UNLCK, 1)
            except OSError:
                pass
        else:
            import fcntl

            try:
                fcntl.flock(self._handle.fileno(), fcntl.LOCK_UN)
            except OSError:
                pass

        self._handle.close()
        self._handle = None
