from __future__ import annotations

import importlib
import os
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Any, cast

ACTIVE_SESSION_MARKER = ".current_session"


class ActiveSessionLeaseError(RuntimeError):
    """The evidence root already has an active writer or cannot be leased."""


@dataclass(slots=True)
class ActiveSessionLease:
    """Cross-process lease that keeps an in-progress session out of replay.

    The marker is intentionally written before the session directory is
    created. Discovery therefore observes either no candidate yet or a
    candidate protected by an already locked marker. A process crash releases
    the OS lock; startup recovery can then remove the stale marker while
    preserving the interrupted evidence directory.
    """

    sessions_root: Path
    session_root: Path
    _descriptor: int
    _marker_identity: tuple[int, int]
    _released: bool = False

    @classmethod
    def acquire(cls, sessions_root: Path, session_root: Path) -> ActiveSessionLease:
        root = sessions_root.expanduser().resolve()
        root.mkdir(mode=0o700, parents=True, exist_ok=True)
        target = session_root.expanduser().absolute()
        if target.parent.resolve() != root or not target.name:
            raise ActiveSessionLeaseError("active session must be a direct child of evidence root")

        marker = root / ACTIVE_SESSION_MARKER
        flags = os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
        try:
            descriptor = os.open(marker, flags, 0o600)
        except FileExistsError as exc:
            raise ActiveSessionLeaseError(
                "another observation session owns the evidence root"
            ) from exc
        try:
            payload = f"{target.name}\n".encode()
            os.write(descriptor, payload)
            os.fsync(descriptor)
            _lock_descriptor(descriptor, blocking=False)
            _fsync_directory(root)
            metadata = os.fstat(descriptor)
            return cls(
                sessions_root=root,
                session_root=target,
                _descriptor=descriptor,
                _marker_identity=(metadata.st_dev, metadata.st_ino),
            )
        except BaseException:
            try:
                marker.unlink(missing_ok=True)
                _fsync_directory(root)
            finally:
                os.close(descriptor)
            raise

    def release(self) -> None:
        if self._released:
            return
        marker = self.sessions_root / ACTIVE_SESSION_MARKER
        try:
            try:
                metadata = marker.lstat()
            except FileNotFoundError:
                metadata = None
            if metadata is not None and (metadata.st_dev, metadata.st_ino) == self._marker_identity:
                marker.unlink()
                _fsync_directory(self.sessions_root)
        finally:
            _unlock_descriptor(self._descriptor)
            os.close(self._descriptor)
            self._released = True

    def __enter__(self) -> ActiveSessionLease:
        return self

    def __exit__(self, *_: object) -> None:
        self.release()


def recover_stale_active_session_marker(sessions_root: Path) -> bool:
    """Remove only an unlocked marker left by a terminated writer."""

    root = sessions_root.expanduser().resolve()
    marker = root / ACTIVE_SESSION_MARKER
    try:
        marker_stat = marker.lstat()
    except FileNotFoundError:
        return False
    except OSError:
        return False
    if not stat.S_ISREG(marker_stat.st_mode) or marker_stat.st_size > 4096:
        return False
    try:
        descriptor = os.open(marker, os.O_RDWR | getattr(os, "O_NOFOLLOW", 0))
    except OSError:
        return False
    locked = False
    try:
        try:
            _lock_descriptor(descriptor, blocking=False)
            locked = True
        except OSError:
            return False
        opened = os.fstat(descriptor)
        try:
            current = marker.lstat()
        except OSError:
            return False
        if (opened.st_dev, opened.st_ino) != (current.st_dev, current.st_ino):
            return False
        marker.unlink()
        _fsync_directory(root)
        return True
    finally:
        if locked:
            _unlock_descriptor(descriptor)
        os.close(descriptor)


def _lock_descriptor(descriptor: int, *, blocking: bool) -> None:
    if os.name == "nt":
        msvcrt = cast(Any, importlib.import_module("msvcrt"))

        os.lseek(descriptor, 0, os.SEEK_SET)
        mode = msvcrt.LK_LOCK if blocking else msvcrt.LK_NBLCK
        msvcrt.locking(descriptor, mode, 1)
        return
    import fcntl

    operation = fcntl.LOCK_EX | (0 if blocking else fcntl.LOCK_NB)
    fcntl.flock(descriptor, operation)


def _unlock_descriptor(descriptor: int) -> None:
    try:
        if os.name == "nt":
            msvcrt = cast(Any, importlib.import_module("msvcrt"))

            os.lseek(descriptor, 0, os.SEEK_SET)
            msvcrt.locking(descriptor, msvcrt.LK_UNLCK, 1)
            return
        import fcntl

        fcntl.flock(descriptor, fcntl.LOCK_UN)
    except OSError:
        return


def _fsync_directory(path: Path) -> None:
    try:
        descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
    except OSError:
        return
    try:
        os.fsync(descriptor)
    finally:
        os.close(descriptor)
