# Ensure only one DyzineFrame instance runs (desktop launcher, runit.sh, SSH, etc.).
from __future__ import annotations

import atexit
import os
import sys
from pathlib import Path
from typing import BinaryIO, Optional, TextIO

_LOCK_NAME = "dyzineframe.lock"
_lock_handle: Optional[TextIO] = None


def _lock_path() -> Path:
    override = os.environ.get("DYZINEFRAME_LOCK", "").strip()
    if override:
        return Path(override)
    base = Path(__file__).resolve().parent
    run_dir = base / "run"
    run_dir.mkdir(exist_ok=True)
    return run_dir / _LOCK_NAME


def _read_holder_pid(path: Path) -> Optional[int]:
    try:
        text = path.read_text(encoding="utf-8").strip()
        if text.isdigit():
            return int(text)
    except OSError:
        pass
    return None


def _pid_alive(pid: int) -> bool:
    if pid <= 0:
        return False
    if sys.platform == "win32":
        import ctypes

        PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
        handle = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
        if handle:
            ctypes.windll.kernel32.CloseHandle(handle)
            return True
        return False
    try:
        os.kill(pid, 0)
    except OSError:
        return False
    return True


def _try_lock(handle: BinaryIO) -> None:
    if sys.platform == "win32":
        import msvcrt

        handle.seek(0)
        msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
        return
    import fcntl

    fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)


def release_singleton() -> None:
    global _lock_handle
    handle = _lock_handle
    _lock_handle = None
    if handle is None:
        return
    try:
        if sys.platform != "win32":
            import fcntl

            fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
    except OSError:
        pass
    try:
        handle.close()
    except OSError:
        pass


def acquire_singleton(*, exit_on_conflict: bool = True) -> bool:
    """Acquire process-wide singleton lock. Returns True if acquired."""
    global _lock_handle

    path = _lock_path()
    stale_pid = _read_holder_pid(path)
    if stale_pid is not None and not _pid_alive(stale_pid):
        try:
            path.unlink(missing_ok=True)
        except OSError:
            pass

    handle = open(path, "a+", encoding="utf-8")
    try:
        _try_lock(handle)
    except (OSError, BlockingIOError):
        handle.close()
        holder = _read_holder_pid(path)
        if holder is not None and not _pid_alive(holder):
            return acquire_singleton(exit_on_conflict=exit_on_conflict)
        msg = (
            f"DyzineFrame already running (pid {holder})"
            if holder is not None
            else "DyzineFrame already running"
        )
        print(msg, flush=True)
        if exit_on_conflict:
            # Exit 2 = peer already holds lock (not a crash). Supervisor must not
            # treat this as quick_crash / OOO backoff.
            sys.exit(2)
        return False

    handle.seek(0)
    handle.truncate()
    handle.write(str(os.getpid()))
    handle.flush()
    _lock_handle = handle
    atexit.register(release_singleton)
    return True
