"""CLI helpers for the ``topovec view`` desktop viewer command."""

from __future__ import annotations

import argparse
import faulthandler
import logging
import os
import platform
import sys

from ...core.log import configLog

logger = logging.getLogger(__name__)

_INTERESTING_ENV_VARS = (
    "QT_QPA_PLATFORM",
    "QT_QPA_PLATFORMTHEME",
    "XDG_SESSION_TYPE",
    "XDG_CURRENT_DESKTOP",
    "DESKTOP_SESSION",
    "DISPLAY",
    "WAYLAND_DISPLAY",
    "EGL_PLATFORM",
    "DRI_PRIME",
    "__NV_PRIME_RENDER_OFFLOAD",
    "__GLX_VENDOR_LIBRARY_NAME",
    "__VK_LAYER_NV_optimus",
    "MESA_LOADER_DRIVER_OVERRIDE",
    "LIBGL_ALWAYS_SOFTWARE",
)


def _load_qt():
    try:
        from PySide6 import QtCore, QtGui, QtWidgets
    except ModuleNotFoundError as exc:
        raise ImportError(
            "topovec view requires the optional GUI stack. Install TopoVec with "
            "`uv tool install 'topovec[view]'`, `uv sync --extra view`, "
            "or `uv sync --all-extras`."
        ) from exc
    return QtCore, QtGui, QtWidgets


def _positive_int(value: str) -> int:
    parsed = int(value)
    if parsed < 1:
        raise argparse.ArgumentTypeError("--max-live-scenes must be at least 1.")
    return parsed


def add_view_arguments(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Enable verbose viewer diagnostics in the console and log dock.",
    )
    parser.add_argument(
        "--max-live-scenes",
        type=_positive_int,
        default=2,
        help="Maximum number of live scene objects to keep resident across viewer systems.",
    )
    parser.add_argument("paths", nargs="*", help="Files to open on startup.")
    return parser


def build_parser() -> argparse.ArgumentParser:
    return add_view_arguments(
        argparse.ArgumentParser(
            prog="topovec view",
            description="Desktop viewer for TopoVec NPZ and SAD state files.",
        )
    )


def _install_console_excepthook() -> object:
    previous = sys.excepthook

    def _hook(exc_type, exc_value, exc_traceback):
        if issubclass(exc_type, KeyboardInterrupt):
            previous(exc_type, exc_value, exc_traceback)
            return
        logger.critical(
            "Unhandled exception in topovec view",
            exc_info=(exc_type, exc_value, exc_traceback),
        )
        previous(exc_type, exc_value, exc_traceback)

    sys.excepthook = _hook
    return previous


def _install_qt_message_logging(QtCore) -> object:
    level_by_type = {
        QtCore.QtMsgType.QtDebugMsg: logging.DEBUG,
        QtCore.QtMsgType.QtInfoMsg: logging.INFO,
        QtCore.QtMsgType.QtWarningMsg: logging.WARNING,
        QtCore.QtMsgType.QtCriticalMsg: logging.ERROR,
        QtCore.QtMsgType.QtFatalMsg: logging.CRITICAL,
    }

    def _handler(message_type, context, message):
        logger.log(level_by_type.get(message_type, logging.INFO), "Qt: %s", message)

    return QtCore.qInstallMessageHandler(_handler)


def _enum_name(enum_value) -> str:
    try:
        return str(enum_value).rsplit(".", 1)[-1]
    except Exception:
        return repr(enum_value)


def _surface_format_snapshot(fmt) -> dict[str, object]:
    return {
        "version": (int(fmt.majorVersion()), int(fmt.minorVersion())),
        "profile": _enum_name(fmt.profile()),
        "renderable_type": _enum_name(fmt.renderableType()),
        "swap_interval": int(fmt.swapInterval()),
        "samples": int(fmt.samples()),
        "depth_buffer_size": int(fmt.depthBufferSize()),
        "stencil_buffer_size": int(fmt.stencilBufferSize()),
        "alpha_buffer_size": int(fmt.alphaBufferSize()),
    }


def _collect_process_environment() -> dict[str, object]:
    return {
        "python": sys.version.split()[0],
        "platform": platform.platform(),
        "machine": platform.machine(),
        "env": {key: os.environ.get(key) for key in _INTERESTING_ENV_VARS},
    }


def _log_startup_environment(configured_logger, QtCore, QtGui, app) -> None:
    snapshot = _collect_process_environment()
    configured_logger.info(
        "topovec view environment platform=%s machine=%s python=%s env=%s",
        snapshot["platform"],
        snapshot["machine"],
        snapshot["python"],
        snapshot["env"],
    )
    screens = []
    for screen in app.screens():
        geometry = screen.geometry()
        screens.append(
            {
                "name": screen.name(),
                "size": (int(geometry.width()), int(geometry.height())),
                "dpr": float(screen.devicePixelRatio()),
            }
        )
    configured_logger.info(
        "Qt runtime platform_name=%s pyside=%s qt=%s default_surface_format=%s screens=%s",
        app.platformName(),
        getattr(QtCore, "__version__", "unknown"),
        QtCore.QLibraryInfo.version().toString(),
        _surface_format_snapshot(QtGui.QSurfaceFormat.defaultFormat()),
        screens,
    )
    if app.platformName() == "wayland":
        configured_logger.warning(
            "topovec view is known to exhibit OpenGL compositing artifacts on Qt Wayland in some Linux setups. "
            "If you see overbright or unstable rendering, rerun with QT_QPA_PLATFORM=xcb."
        )


def run_view(args: argparse.Namespace, *, argv: list[str] | None = None) -> int:
    configured_logger = configLog(logging.DEBUG if args.debug else logging.INFO)
    configured_logger.info(
        "Starting topovec view debug=%s startup_paths=%s max_live_scenes=%s",
        bool(args.debug),
        list(args.paths),
        int(args.max_live_scenes),
    )
    faulthandler.enable(file=sys.stderr, all_threads=True)
    previous_excepthook = _install_console_excepthook()
    previous_qt_message_handler = None
    try:
        QtCore, QtGui, QtWidgets = _load_qt()
        previous_qt_message_handler = _install_qt_message_logging(QtCore)
        from .viewport import QtViewport
        from .main_window import TvViewWindow

        app = QtWidgets.QApplication.instance()
        owns_app = app is None
        if app is None:
            configured_logger.debug("Creating QApplication for topovec view")
            QtGui.QSurfaceFormat.setDefaultFormat(QtViewport.create_surface_format())
            QtWidgets.QApplication.setApplicationName("topovec view")
            QtWidgets.QApplication.setOrganizationName("topovec")
            QtWidgets.QApplication.setApplicationDisplayName("topovec view")
            QtWidgets.QApplication.setAttribute(
                QtCore.Qt.ApplicationAttribute.AA_ShareOpenGLContexts
            )
            QtWidgets.QApplication.setAttribute(
                QtCore.Qt.ApplicationAttribute.AA_UseDesktopOpenGL
            )
            QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy(
                QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
            )
            app = QtWidgets.QApplication([sys.argv[0], *(argv or [])])
        else:
            configured_logger.debug("Reusing existing QApplication for topovec view")
        _log_startup_environment(configured_logger, QtCore, QtGui, app)

        configured_logger.debug("Constructing TvViewWindow")
        window = TvViewWindow(max_live_scenes=int(args.max_live_scenes))
        configured_logger.debug("TvViewWindow constructed")
        window.show()
        configured_logger.debug("TvViewWindow shown")
        if args.paths:
            window.load_paths(list(args.paths))
        if owns_app:
            configured_logger.debug("Entering topovec view event loop")
            return int(app.exec())
        return 0
    except Exception:
        configured_logger.exception("topovec view startup failed")
        return 1
    finally:
        if previous_qt_message_handler is not None:
            QtCore.qInstallMessageHandler(previous_qt_message_handler)
        sys.excepthook = previous_excepthook


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    return run_view(args, argv=argv)


__all__ = ["add_view_arguments", "build_parser", "main", "run_view"]
