"""Protection multi-instance de la GUI V6 (P0-7).

- Windows (frozen) : mutex nommé kernel via ctypes — ce nom DEVRA être déclaré comme
  ``AppMutex`` dans installer/Anonymisation.iss (Plan 3 / D8) pour que l'installeur
  ferme l'app avant une mise à jour.
- POSIX (dev/test) : verrou ``fcntl`` exclusif sur un fichier dans le dossier app.

Précondition D8 : la GUI n'a pas de réduction en zone de notification (tray). L'AppMutex
+ ``CloseApplications`` de l'installeur suffisent car fermer la fenêtre termine le process.
Si un mode tray était ajouté, le process survivrait au ``WM_CLOSE`` et D8 devrait être revu.
"""

from __future__ import annotations

import os
import sys
from pathlib import Path

# Nom destiné à l'AppMutex de installer/Anonymisation.iss (Plan 3 / D8). NE PAS modifier sans synchroniser le .iss.
APP_MUTEX_NAME = "AivanonymAnonymisationV6"


class AlreadyRunningError(RuntimeError):
    """Une autre instance de l'application est déjà en cours d'exécution."""


def _lock_dir() -> Path:
    base = os.environ.get("LOCALAPPDATA")
    root = Path(base) if base else Path.home() / ".local" / "share"
    d = root / "Aivanov" / "Anonymisation"
    d.mkdir(parents=True, exist_ok=True)
    return d


class SingleInstance:
    def __init__(self) -> None:
        self._handle = None  # mutex Windows
        self._fh = None       # file handle POSIX

    def acquire(self) -> None:
        if sys.platform.startswith("win"):
            self._acquire_windows()
        else:
            self._acquire_posix()

    def _acquire_windows(self) -> None:  # pragma: no cover (exécuté sur Windows)
        import ctypes

        ERROR_ALREADY_EXISTS = 183
        handle = ctypes.windll.kernel32.CreateMutexW(None, False, APP_MUTEX_NAME)
        if not handle or ctypes.windll.kernel32.GetLastError() == ERROR_ALREADY_EXISTS:
            raise AlreadyRunningError("L'application est déjà ouverte.")
        self._handle = handle

    def _acquire_posix(self) -> None:
        import fcntl

        path = _lock_dir() / "instance.lock"
        fh = open(path, "w")
        try:
            fcntl.flock(fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        except OSError:
            fh.close()
            raise AlreadyRunningError("L'application est déjà ouverte.")
        self._fh = fh

    def release(self) -> None:
        if self._handle is not None:  # pragma: no cover
            import ctypes

            ctypes.windll.kernel32.CloseHandle(self._handle)
            self._handle = None
        if self._fh is not None:
            self._fh.close()
            self._fh = None
