# -*- coding: utf-8 -*-
#!/usr/bin/env python3

import sys
import os
import inspect
import importlib

from Qt import QtWidgets, QtCore, QtGui

# ------------------------------------------------------------------
# MONKEY-PATCH to fix "module 'qtpy.QtGui' has no attribute 'QUndoStack'"
try:
    from qtpy.QtWidgets import QUndoStack
    import qtpy
    qtpy.QtGui.QUndoStack = QUndoStack
except ImportError:
    print("WARNING: Could not monkey-patch QUndoStack. You may see an error if OdenGraphQt needs it.")
# ------------------------------------------------------------------

# ------------------------------------------------------------------
# MONKEY-PATCH to fix "'BackdropNodeItem' object has no attribute 'widgets'"
try:
    from OdenGraphQt.nodes.backdrop_node import BackdropNodeItem
    if not hasattr(BackdropNodeItem, "widgets"):
        BackdropNodeItem.widgets = {}
except ImportError:
    print("WARNING: Could not monkey-patch BackdropNodeItem to add `widgets`.")
# ------------------------------------------------------------------

# Import your data_manager so we can start the Flask server
from Modules import data_manager
data_manager.start_api_server()

# --- BEGIN ROBUST PATCH FOR QGraphicsScene.setSelectionArea ---
_original_setSelectionArea = QtWidgets.QGraphicsScene.setSelectionArea

def _patched_setSelectionArea(self, *args, **kwargs):
    """
    A robust patch that handles various call signatures for QGraphicsScene.setSelectionArea().
    """
    try:
        return _original_setSelectionArea(self, *args, **kwargs)
    except TypeError:
        if not args:
            raise
        painterPath = args[0]
        selection_op = QtCore.Qt.ReplaceSelection
        selection_mode = QtCore.Qt.IntersectsItemShape
        transform = QtGui.QTransform()
        return _original_setSelectionArea(self, painterPath, selection_op, selection_mode, transform)

QtWidgets.QGraphicsScene.setSelectionArea = _patched_setSelectionArea
# --- END PATCH ---

from OdenGraphQt import NodeGraph, BaseNode
from OdenGraphQt.widgets.dialogs import FileDialog


def ensure_workflows_folder():
    """
    Ensures a 'Workflows' subfolder exists.
    """
    if not os.path.exists("Workflows"):
        os.makedirs("Workflows")


def close_workflow(graph: NodeGraph):
    """
    Closes the current workflow (removes all nodes and connections).
    """
    graph.clear_session()


def save_workflow(graph: NodeGraph):
    """
    Saves the current workflow (including custom names, positions, wires, etc.)
    into a JSON file in the 'Workflows' subfolder.
    """
    ensure_workflows_folder()
    file_filter = "JSON Files (*.json);;All Files (*.*)"
    dlg = FileDialog.getSaveFileName(None, "Save Workflow", os.path.join("Workflows", ""), file_filter)
    file_path = dlg[0]
    if not file_path:
        return  # User canceled

    if not file_path.lower().endswith(".json"):
        file_path += ".json"

    try:
        graph.save_session(file_path)
        print(f"Workflow saved to {file_path}")
    except Exception as e:
        QtWidgets.QMessageBox.critical(None, "Error Saving Workflow", str(e))


def load_workflow(graph: NodeGraph):
    """
    Loads a workflow (including node values, connections, positions, etc.)
    from a specified JSON file, then centers the view on all loaded nodes.
    """
    ensure_workflows_folder()
    file_filter = "JSON Files (*.json);;All Files (*.*)"
    dlg = FileDialog.getOpenFileName(None, "Load Workflow", os.path.join("Workflows", ""), file_filter)
    file_path = dlg[0]
    if not file_path:
        return  # User canceled

    try:
        graph.load_session(file_path)
        # After loading, center the viewer on all nodes.
        all_nodes = graph.all_nodes()
        if all_nodes:
            graph.viewer().zoom_to_nodes([node.view for node in all_nodes])
        print(f"Workflow loaded from {file_path}")
    except Exception as e:
        QtWidgets.QMessageBox.critical(None, "Error Loading Workflow", str(e))


def import_nodes_from_folder(package_name):
    """
    Recursively import all modules from the given package.
    Returns a dictionary where keys are subfolder names,
    and values are lists of BaseNode subclasses.
    """
    nodes_by_category = {}
    package = importlib.import_module(package_name)
    package_path = package.__path__[0]

    for root, _, files in os.walk(package_path):
        rel_path = os.path.relpath(root, package_path).replace(os.sep, '.')
        module_prefix = f"{package_name}.{rel_path}" if rel_path != '.' else package_name
        category_name = os.path.basename(root)

        for file in files:
            if file.endswith(".py") and file != "__init__.py":
                module_name = f"{module_prefix}.{file[:-3]}"
                try:
                    module = importlib.import_module(module_name)
                    for name, obj in inspect.getmembers(module, inspect.isclass):
                        if issubclass(obj, BaseNode) and obj.__module__ == module.__name__:
                            if category_name not in nodes_by_category:
                                nodes_by_category[category_name] = []
                            nodes_by_category[category_name].append(obj)
                except Exception as e:
                    print(f"Failed to import {module_name}: {e}")

    return nodes_by_category


def make_node_command(graph, node_type_str):
    """
    Return a function that creates a node of the given type at the current cursor position.
    Ensures that only one FlyffCharacterStatusNode exists if that's the node being created.
    """
    def real_create():
        if node_type_str.startswith("bunny-lab.io.flyff_character_status_node"):
            for node in graph.all_nodes():
                if node.__class__.__name__ == "FlyffCharacterStatusNode":
                    QtWidgets.QMessageBox.critical(
                        None,
                        "Error",
                        "Only one Flyff Character Status Collector node is allowed."
                    )
                    return
        try:
            pos = graph.cursor_pos()
            graph.create_node(node_type_str, pos=pos)
        except Exception as e:
            QtWidgets.QMessageBox.critical(None, "Error", str(e))

    def command():
        if QtWidgets.QApplication.instance():
            real_create()
        else:
            QtCore.QTimer.singleShot(0, real_create)

    return command


class BorealisWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Project Borealis - Workflow Automation System")

        # Create NodeGraph and widget
        self.graph = NodeGraph()

        # Grid styling changes
        self.graph.set_background_color(20, 20, 20)  # Dark gray
        self.graph.set_grid_color(60, 60, 60)        # Gray grid lines

        # Add gradient background
        scene = self.graph.scene()
        gradient = QtGui.QLinearGradient(0, 0, 0, 1)
        gradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        gradient.setColorAt(0.0, QtGui.QColor(9, 44, 68))
        gradient.setColorAt(0.3, QtGui.QColor(30, 30, 30))
        gradient.setColorAt(0.7, QtGui.QColor(30, 30, 30))
        gradient.setColorAt(1.0, QtGui.QColor(9, 44, 68))
        scene.setBackgroundBrush(QtGui.QBrush(gradient))

        # Load custom nodes from "Nodes" folder
        custom_nodes_by_category = import_nodes_from_folder("Nodes")
        for category, node_classes in custom_nodes_by_category.items():
            for node_class in node_classes:
                self.graph.register_node(node_class)

        # Build the right-click context menu for the graph
        self._build_graph_context_menu(custom_nodes_by_category)

        # Setup the central widget to show the NodeGraph
        self.setCentralWidget(self.graph.widget)

        # Build the top menu bar
        self._build_menubar()

        # Create a status bar
        self.setStatusBar(QtWidgets.QStatusBar(self))
        # Remove the resize handle from the status bar:
        self.statusBar().setSizeGripEnabled(False)

        # Add a permanent clickable link to the right side of the status bar:
        self.link_label = QtWidgets.QLabel()
        self.link_label.setTextFormat(QtCore.Qt.RichText)
        self.link_label.setOpenExternalLinks(True)
        # Add a couple spaces after the URL using  
        # Also color style to match your "blue-ish" highlight:
        self.link_label.setText(
            "<a href='http://127.0.0.1:5000/data' "
            "style='color: rgb(60,120,180); text-decoration: none;'>"
            "Flask API Server: http://127.0.0.1:5000/data&nbsp;&nbsp;</a>"
        )
        self.statusBar().addPermanentWidget(self.link_label)

        # Resize
        self.resize(1200, 800)

    def _build_graph_context_menu(self, custom_nodes_by_category):
        """
        Build context menu and apply custom stylesheet for the 'blue-ish' highlight.
        """
        graph_context_menu = self.graph.get_context_menu("graph")
        menu_stylesheet = """
        QMenu {
            background-color: rgb(30, 30, 30);
            border: 1px solid rgba(200, 200, 200, 60);
        }
        QMenu::item {
            padding: 5px 18px 2px;
            background-color: transparent;
        }
        QMenu::item:selected {
            color: rgb(255, 255, 255);
            background-color: rgba(60, 120, 180, 150);
        }
        QMenu::separator {
            height: 1px;
            background: rgba(255, 255, 255, 50);
            margin: 4px 8px;
        }
        """
        if graph_context_menu and graph_context_menu.qmenu:
            graph_context_menu.qmenu.setStyleSheet(menu_stylesheet)

        add_nodes_menu = graph_context_menu.add_menu("Add Nodes")
        if add_nodes_menu and add_nodes_menu.qmenu:
            add_nodes_menu.qmenu.setStyleSheet(menu_stylesheet)

        for category, node_classes in custom_nodes_by_category.items():
            category_menu = add_nodes_menu.add_menu(category)
            if category_menu and category_menu.qmenu:
                category_menu.qmenu.setStyleSheet(menu_stylesheet)

            for node_class in node_classes:
                node_type = f"{node_class.__identifier__}.{node_class.__name__}"
                node_name = node_class.NODE_NAME
                category_menu.add_command(
                    node_name,
                    make_node_command(self.graph, node_type)
                )

        graph_context_menu.add_command(
            "Remove Selected Node",
            lambda: [self.graph.remove_node(node)
                     for node in self.graph.selected_nodes()]
            if self.graph.selected_nodes() else None
        )

    def _build_menubar(self):
        menubar = self.menuBar()

        # Workflows menu
        workflows_menu = menubar.addMenu("Workflows")

        load_action = QtWidgets.QAction("Load Workflow", self)
        load_action.triggered.connect(lambda: load_workflow(self.graph))
        workflows_menu.addAction(load_action)

        save_action = QtWidgets.QAction("Save Workflow", self)
        save_action.triggered.connect(lambda: save_workflow(self.graph))
        workflows_menu.addAction(save_action)

        close_action = QtWidgets.QAction("Close Workflow", self)
        close_action.triggered.connect(lambda: close_workflow(self.graph))
        workflows_menu.addAction(close_action)

        # About menu
        about_menu = menubar.addMenu("About")

        gitea_action = QtWidgets.QAction("Gitea Project", self)
        gitea_action.triggered.connect(self._open_gitea_project)
        about_menu.addAction(gitea_action)

        credits_action = QtWidgets.QAction("Credits", self)
        credits_action.triggered.connect(self._show_credits_popup)
        about_menu.addAction(credits_action)

        updates_action = QtWidgets.QAction("Check for Updates", self)
        updates_action.triggered.connect(self._show_updates_popup)
        about_menu.addAction(updates_action)

    def _open_gitea_project(self):
        url = QtCore.QUrl("https://git.bunny-lab.io/Scripts/Project_Borealis")
        QtGui.QDesktopServices.openUrl(url)

    def _show_credits_popup(self):
        QtWidgets.QMessageBox.information(
            self,
            "Credits",
            "Created by Nicole Rappe"
        )

    def _show_updates_popup(self):
        QtWidgets.QMessageBox.information(
            self,
            "Check for Updates",
            "Built-in update functionality has not been built yet, but it's on the roadmap. Stay tuned."
        )


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = BorealisWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()