"""Single-instance lock for Agent Console (prevents duplicate splash windows)."""

from __future__ import annotations

import atexit
import fcntl
import os
import signal
import time
from collections.abc import Callable
from pathlib import Path

DUPLICATE_INSTANCE_EXIT_CODE = 5


def default_console_lock_path() -> Path:
    return Path.home() / ".config" / "iuve-connect-agent" / "console.lock"


def default_activation_flag_path() -> Path:
    return Path.home() / ".config" / "iuve-connect-agent" / "console-activate.flag"


class ConsoleInstanceLock:
    """Non-blocking flock lock for one console UI process per user session."""

    def __init__(self, lock_path: Path | None = None) -> None:
        self._path = (lock_path or default_console_lock_path()).expanduser()
        self._fd: int | None = None

    @property
    def lock_path(self) -> Path:
        return self._path

    def acquire(self) -> bool:
        self._path.parent.mkdir(parents=True, exist_ok=True)
        fd = os.open(str(self._path), os.O_RDWR | os.O_CREAT, 0o600)
        try:
            fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except BlockingIOError:
            os.close(fd)
            return False
        os.ftruncate(fd, 0)
        os.write(fd, str(os.getpid()).encode("ascii"))
        self._fd = fd
        return True

    def release(self) -> None:
        if self._fd is None:
            return
        try:
            fcntl.flock(self._fd, fcntl.LOCK_UN)
        except Exception:
            pass
        try:
            os.close(self._fd)
        except Exception:
            pass
        self._fd = None


def read_running_console_pid(lock_path: Path | None = None) -> int | None:
    """Read console PID from lock file without acquiring the lock."""
    path = (lock_path or default_console_lock_path()).expanduser()
    if not path.is_file():
        return None
    try:
        text = path.read_text(encoding="ascii").strip()
        pid = int(text)
        return pid if pid > 0 else None
    except Exception:
        return None


def request_console_activation() -> None:
    """Signal the running console to raise its main window."""
    path = default_activation_flag_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(str(time.time()), encoding="ascii")


def consume_console_activation_request() -> bool:
    path = default_activation_flag_path()
    if not path.is_file():
        return False
    try:
        path.unlink()
    except Exception:
        return False
    return True


def duplicate_instance_exit_code() -> int:
    return DUPLICATE_INSTANCE_EXIT_CODE


def try_acquire_console_or_raise_existing(
    *,
    on_duplicate: Callable[[], None],
) -> ConsoleInstanceLock | None:
    lock = ConsoleInstanceLock()
    if lock.acquire():
        return lock
    on_duplicate()
    request_console_activation()
    return None


_active_console_lock: ConsoleInstanceLock | None = None


def _release_active_console_lock() -> None:
    global _active_console_lock
    if _active_console_lock is None:
        return
    try:
        _active_console_lock.release()
    except Exception:
        pass
    _active_console_lock = None


def _console_signal_handler(signum: int, _frame) -> None:
    _release_active_console_lock()
    raise SystemExit(128 + int(signum))


def install_console_lock_cleanup(lock: ConsoleInstanceLock) -> None:
    """Release console.lock on normal exit, crash, or termination signal."""
    global _active_console_lock
    _active_console_lock = lock
    atexit.register(_release_active_console_lock)
    for sig_name in ("SIGTERM", "SIGINT", "SIGHUP"):
        sig = getattr(signal, sig_name, None)
        if sig is None:
            continue
        try:
            signal.signal(sig, _console_signal_handler)
        except (ValueError, OSError):
            pass
