"""PID/lock/singleton helpers for host core process management."""

import os
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path

_MODULE_NAME = __name__


def _is_pid_alive(pid: int) -> bool:
    if pid <= 0:
        return False
    try:
        os.kill(pid, 0)
        return True
    except Exception:
        return False


def _list_running_host_core_pids(exclude_pid: int | None = None) -> list[int]:
    current_pid = int(exclude_pid or 0)
    try:
        result = subprocess.run(
            ["ps", "-axo", "pid=,args="],
            capture_output=True,
            text=True,
        )
    except Exception:
        return []
    if result.returncode != 0:
        return []

    pids: list[int] = []
    for raw_line in (result.stdout or "").splitlines():
        line = raw_line.strip()
        if not line:
            continue
        pid_text, _, args = line.partition(" ")
        try:
            pid = int(pid_text.strip())
        except Exception:
            continue
        normalized_args = " ".join(args.split())
        if "src/armorer/core.py" not in normalized_args:
            continue
        if current_pid and pid == current_pid:
            continue
        pids.append(pid)
    return pids


@contextmanager
def _advisory_lock(lock_path: Path):
    lock_path.parent.mkdir(parents=True, exist_ok=True)
    handle = lock_path.open("a+")
    try:
        try:
            import fcntl

            fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
        except Exception:
            pass
        yield
    finally:
        try:
            try:
                import fcntl

                fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
            except Exception:
                pass
            handle.close()
        except Exception:
            pass


def _release_owned_pid_file(pid_file: Path, pid: int) -> None:
    try:
        if pid_file.exists() and pid_file.read_text().strip() == str(pid):
            pid_file.unlink()
    except Exception:
        pass


def _claim_host_core_singleton(config_dir: Path, *, pid: int | None = None) -> bool:
    # Resolve helpers through the package namespace so that monkeypatch on
    # ``armorer.core._is_pid_alive`` / ``armorer.core._list_running_host_core_pids``
    # takes effect in tests without requiring test changes.
    _mod = sys.modules.get("armorer.core", sys.modules[_MODULE_NAME])
    _pid_alive = getattr(_mod, "_is_pid_alive", _is_pid_alive)
    _list_pids = getattr(_mod, "_list_running_host_core_pids", _list_running_host_core_pids)

    owner_pid = int(pid or os.getpid())
    pid_file = config_dir / "core-host.pid"
    lock_file = config_dir / "core-host.lock"

    with _advisory_lock(lock_file):
        if pid_file.exists():
            try:
                existing_pid = int(pid_file.read_text().strip() or "0")
            except Exception:
                existing_pid = 0
            if existing_pid and existing_pid != owner_pid and _pid_alive(existing_pid):
                return False
            if existing_pid != owner_pid:
                try:
                    pid_file.unlink()
                except Exception:
                    pass

        running_pids = _list_pids(exclude_pid=owner_pid)
        if running_pids:
            try:
                pid_file.write_text(str(running_pids[0]))
            except Exception:
                pass
            return False

        pid_file.write_text(str(owner_pid))
        return True
