#  Copyright 2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""Unit tests for GUI exception handling."""

from __future__ import annotations

import sys
import threading

import pytest

# Skip this module if PySide6 is not available
pytest.importorskip('PySide6', reason='PySide6 is required for GUI tests')

from PySide6.QtCore import QEventLoop, QTimer
from PySide6.QtWidgets import QApplication

from mafw.steering_gui.utils import exception_handling

pytestmark = pytest.mark.gui


@pytest.fixture
def qapp() -> QApplication:
    """Ensure a single QApplication instance exists for all GUI tests."""
    app = QApplication.instance()
    if app is None:
        app = QApplication([])
    return app


def test_install_exception_handler_registers_hooks(monkeypatch: pytest.MonkeyPatch) -> None:
    """Ensure installing the handler updates sys and thread hooks."""
    original_sys_hook = sys.excepthook
    original_thread_hook = getattr(threading, 'excepthook', None)

    def _fake_dialog(*_args: object) -> None:
        return None

    monkeypatch.setattr(exception_handling, 'show_exception_dialog', _fake_dialog)
    exception_handling._thread_exception_handler = None

    try:
        returned_hook = exception_handling.install_exception_handler()
        assert returned_hook is original_sys_hook
        assert sys.excepthook is not original_sys_hook
        if original_thread_hook is not None:
            assert threading.excepthook is not original_thread_hook
    finally:
        sys.excepthook = original_sys_hook
        if original_thread_hook is not None:
            threading.excepthook = original_thread_hook


def test_thread_exception_emits_signal(qapp: QApplication, monkeypatch: pytest.MonkeyPatch) -> None:
    """Ensure thread exceptions are forwarded to the GUI handler."""
    original_sys_hook = sys.excepthook
    original_thread_hook = getattr(threading, 'excepthook', None)

    def _fake_dialog(*_args: object) -> None:
        return None

    monkeypatch.setattr(exception_handling, 'show_exception_dialog', _fake_dialog)
    exception_handling._thread_exception_handler = None

    try:
        exception_handling.install_exception_handler()
        handler = exception_handling.get_thread_exception_handler()

        captured: dict[str, object] = {}
        loop = QEventLoop()

        def _capture(exc_type: object, exc_value: object, _tb: object) -> None:
            captured['type'] = exc_type
            captured['value'] = exc_value
            loop.quit()

        handler.exception_caught.connect(_capture)

        def _raise_error() -> None:
            raise RuntimeError('boom')

        thread = threading.Thread(target=_raise_error, daemon=True)
        thread.start()

        QTimer.singleShot(3000, loop.quit)
        loop.exec()
        thread.join(timeout=1)

        assert captured.get('type') is RuntimeError
        assert isinstance(captured.get('value'), RuntimeError)
    finally:
        sys.excepthook = original_sys_hook
        if original_thread_hook is not None:
            threading.excepthook = original_thread_hook
