from __future__ import annotations

import logging.handlers
import os
import platform
import sys
import traceback
import types

import settngs

from comictaggerlib.ctsettings import ct_ns
from comictaggerlib.graphics import graphics_path
from comictalker.comictalker import ComicTalker

logger = logging.getLogger("comictagger")
try:
    qt_available = True
    from PyQt5 import QtCore, QtGui, QtWidgets

    def show_exception_box(log_msg: str) -> None:
        """Checks if a QApplication instance is available and shows a messagebox with the exception message.
        If unavailable (non-console application), log an additional notice.
        """
        if QtWidgets.QApplication.instance() is not None:
            errorbox = QtWidgets.QMessageBox()
            errorbox.setStandardButtons(
                QtWidgets.QMessageBox.StandardButton.Abort | QtWidgets.QMessageBox.StandardButton.Ignore
            )
            errorbox.setText(log_msg)
            if errorbox.exec() == QtWidgets.QMessageBox.StandardButton.Abort:
                QtWidgets.QApplication.exit(1)
            else:
                logger.warning("Exception ignored")
        else:
            logger.debug("No QApplication instance available.")

    class UncaughtHook(QtCore.QObject):
        _exception_caught = QtCore.pyqtSignal(object)

        def __init__(self) -> None:
            super().__init__()

            # this registers the exception_hook() function as hook with the Python interpreter
            sys.excepthook = self.exception_hook

            # connect signal to execute the message box function always on main thread
            self._exception_caught.connect(show_exception_box)

        def exception_hook(
            self, exc_type: type[BaseException], exc_value: BaseException, exc_traceback: types.TracebackType | None
        ) -> None:
            """Function handling uncaught exceptions.
            It is triggered each time an uncaught exception occurs.
            """
            if issubclass(exc_type, KeyboardInterrupt):
                # ignore keyboard interrupt to support console applications
                sys.__excepthook__(exc_type, exc_value, exc_traceback)
            else:
                exc_info = (exc_type, exc_value, exc_traceback)
                trace_back = "".join(traceback.format_tb(exc_traceback))
                log_msg = f"{exc_type.__name__}: {exc_value}\n\n{trace_back}"
                logger.critical("Uncaught exception: %s: %s", exc_type.__name__, exc_value, exc_info=exc_info)

                # trigger message box show
                self._exception_caught.emit(f"Oops. An unexpected error occurred:\n{log_msg}")

    qt_exception_hook = UncaughtHook()
    from comictaggerlib.taggerwindow import TaggerWindow

    try:
        # needed here to initialize QWebEngine
        from PyQt5.QtWebEngineWidgets import QWebEngineView  # noqa: F401

        qt_webengine_available = True
    except ImportError:
        qt_webengine_available = False

    class Application(QtWidgets.QApplication):
        openFileRequest = QtCore.pyqtSignal(QtCore.QUrl, name="openfileRequest")

        # Handles "Open With" from Finder on macOS
        def event(self, event: QtCore.QEvent) -> bool:
            if event.type() == QtCore.QEvent.FileOpen:
                logger.info(event.url().toLocalFile())
                self.openFileRequest.emit(event.url())
                return True
            return super().event(event)

except ImportError as e:

    def show_exception_box(log_msg: str) -> None: ...

    logger.exception("Qt unavailable")
    qt_available = False
    import_error = e


def open_tagger_window(
    talkers: dict[str, ComicTalker], config: settngs.Config[ct_ns], error: tuple[str, bool] | None
) -> None:
    os.environ["QtWidgets.QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
    args = [sys.argv[0]]
    if config[0].Runtime_Options__darkmode:
        args.extend(["-platform", "windows:darkmode=2"])
    app = Application(args)
    if error is not None:
        show_exception_box(error[0])
        if error[1]:
            raise SystemExit(1)

    # needed to catch initial open file events (macOS)
    app.openFileRequest.connect(lambda x: config[0].Runtime_Options__files.append(x.toLocalFile()))
    # The window Icon needs to be set here. It's also set in taggerwindow.ui but it doesn't seem to matter
    app.setWindowIcon(QtGui.QIcon(":/graphics/app.png"))

    if platform.system() == "Windows":
        # For pure python, tell windows that we're not python,
        # so we can have our own taskbar icon
        import ctypes

        myappid = "comictagger"  # arbitrary string
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)  # type: ignore[attr-defined]
        # force close of console window
        swp_hidewindow = 0x0080
        console_wnd = ctypes.windll.kernel32.GetConsoleWindow()  # type: ignore[attr-defined]
        if console_wnd != 0:
            ctypes.windll.user32.SetWindowPos(console_wnd, None, 0, 0, 0, 0, swp_hidewindow)  # type: ignore[attr-defined]

    if platform.system() != "Linux":
        img = QtGui.QPixmap(str(graphics_path / "tags.png"))

        splash = QtWidgets.QSplashScreen(img)
        splash.show()
        splash.raise_()
        QtWidgets.QApplication.processEvents()

    try:
        tagger_window = TaggerWindow(config[0].Runtime_Options__files, config, talkers)
        tagger_window.show()

        # Catch open file events (macOS)
        app.openFileRequest.connect(tagger_window.open_file_event)

        if platform.system() != "Linux":
            splash.finish(tagger_window)

        sys.exit(app.exec())
    except Exception:
        logger.exception("GUI mode failed")
        QtWidgets.QMessageBox.critical(
            QtWidgets.QMainWindow(), "Error", "Unhandled exception in app:\n" + traceback.format_exc()
        )
