"""Nur ein Jam: PID-Datei + optional Fenster nach vorne holen."""

from __future__ import annotations

import atexit
import os
import sys
from pathlib import Path

import psutil

from PyQt6.QtCore import QByteArray
from PyQt6.QtNetwork import QLocalServer, QLocalSocket
from PyQt6.QtWidgets import QApplication

_ROOT = Path(__file__).resolve().parent.parent
_PID_FILE = _ROOT / "data" / "jam.pid"
_SERVER_KEY = "bella_italia_jam_buddy_v1"
_PING = QByteArray(b"show")
_JAM_ROOT = "jam-buddy"


def _ensure_qt_app() -> QApplication:
    app = QApplication.instance()
    if app is None:
        app = QApplication(sys.argv)
    return app


def _is_jam_process(proc: psutil.Process) -> bool:
    try:
        name = (proc.name() or "").lower()
        if name not in ("pythonw.exe", "python.exe"):
            return False
        cmd = " ".join(proc.cmdline() or [])
        if "main.py" not in cmd and "jam_watchdog.py" not in cmd:
            return False
        cwd = (proc.cwd() or "").lower()
        return _JAM_ROOT in cwd or _JAM_ROOT in cmd.lower()
    except (psutil.Error, OSError):
        return False


def _pid_alive(pid: int) -> bool:
    if pid <= 0 or not psutil.pid_exists(pid):
        return False
    try:
        return _is_jam_process(psutil.Process(pid))
    except psutil.Error:
        return False


def _read_pid() -> int | None:
    try:
        return int(_PID_FILE.read_text(encoding="utf-8").strip())
    except (OSError, ValueError):
        return None


def _write_pid() -> None:
    _PID_FILE.parent.mkdir(parents=True, exist_ok=True)
    _PID_FILE.write_text(str(os.getpid()), encoding="utf-8")


def _remove_pid() -> None:
    try:
        if _read_pid() == os.getpid():
            _PID_FILE.unlink(missing_ok=True)
    except OSError:
        pass


def stop_other_jam_processes() -> int:
    """Beendet andere Jam-main.py-Prozesse (nicht diesen)."""
    me = os.getpid()
    victims: list[psutil.Process] = []
    for proc in psutil.process_iter(["pid", "name", "cmdline", "cwd"]):
        if proc.pid == me or not _is_jam_process(proc):
            continue
        victims.append(proc)
    for proc in victims:
        try:
            proc.terminate()
        except psutil.Error:
            pass
    if victims:
        psutil.wait_procs(victims, timeout=4)
    return len(victims)


def claim_singleton() -> bool:
    """
    True = diese Instanz darf starten.
    False = ein anderer Jam läuft schon.
    """
    old = _read_pid()
    if old is not None and old != os.getpid() and _pid_alive(old):
        return False
    _write_pid()
    atexit.register(_remove_pid)
    return True


def _clear_stale_server() -> None:
    QLocalServer.removeServer(_SERVER_KEY)


def ping_running_jam() -> bool:
    """Bestehenden Jam nach vorne holen, wenn er antwortet."""
    _ensure_qt_app()
    sock = QLocalSocket()
    sock.connectToServer(_SERVER_KEY)
    if not sock.waitForConnected(400):
        return False
    sock.write(_PING)
    if not sock.waitForBytesWritten(400):
        sock.disconnectFromServer()
        return False
    if not sock.waitForReadyRead(800):
        sock.disconnectFromServer()
        _clear_stale_server()
        return False
    alive = bytes(sock.readAll()) == b"ok"
    sock.disconnectFromServer()
    if not alive:
        _clear_stale_server()
    return alive


def another_instance_running() -> bool:
    pid = _read_pid()
    if pid is not None and pid != os.getpid() and _pid_alive(pid):
        return True
    return ping_running_jam()


def request_existing_window() -> bool:
    if not another_instance_running():
        return False
    return ping_running_jam()


def bind_singleton_server(on_show) -> QLocalServer | None:
    if another_instance_running():
        return None

    def _on_connection() -> None:
        conn = server.nextPendingConnection()
        if conn is None:
            return
        if conn.waitForReadyRead(300):
            conn.readAll()
        on_show()
        conn.write(QByteArray(b"ok"))
        conn.waitForBytesWritten(300)
        conn.disconnectFromServer()

    server = QLocalServer()
    for _ in range(4):
        _clear_stale_server()
        if server.listen(_SERVER_KEY):
            server.newConnection.connect(_on_connection)
            return server
    return None
