import logging
from PySide6.QtWidgets import QDialog, QTreeWidgetItem, QCheckBox, QMessageBox, QWidget, QHBoxLayout
from PySide6.QtCore import Qt
from pathlib import Path

from ui.XmlToXslAssignDialog_ui import Ui_XmlToXslAssignDialog
from conf import TreeNode, XslFile


logger = logging.getLogger(__name__)


class XmlToXslAssignDialog(QDialog):
    """Dialog zur Zuordnung einer XML-Datei zu XSL-Knoten."""

    def __init__(
        self,
        parent=None,
        xml_file_path=None,
        project_nodes=None,
        preselected_xsl_ids: set | None = None,
        edit_mode: bool = False,
    ):
        """
        Initialisiert den Dialog.

        Args:
            parent: Übergeordnetes Widget
            xml_file_path: Pfad zur XML-Datei
            project_nodes: Liste der Projekt-Knoten
            preselected_xsl_ids: Set von XslFile-IDs (tuple), deren Checkbox initial angehakt sein soll
            edit_mode: True = Bearbeiten-Modus (Zuordnungen ändern), False = Neu-Zuordnen
        """
        super().__init__(parent)

        # UI einrichten
        self.ui = Ui_XmlToXslAssignDialog()
        self.ui.setupUi(self)

        # Parameter speichern
        self.xml_file_path = Path(xml_file_path) if xml_file_path else None
        self.project_nodes = project_nodes or []
        self.preselected_xsl_ids: set = set(preselected_xsl_ids) if preselected_xsl_ids else set()
        self.edit_mode = edit_mode

        # Dictionary zum Speichern der Checkbox-Referenzen
        self.xsl_checkboxes = {}  # {python_id(node): checkbox}
        self.xsl_nodes = {}  # {python_id(node): XslFile}
        # Initial ausgewählte XslFile-IDs (tuple), um Diff beim Accept zu berechnen
        self._initial_selected_ids: set = set()

        # Edit-Modus: UI anpassen
        if self.edit_mode:
            self.setWindowTitle("XML-Zuordnungen bearbeiten")
            self.ui.infoLabel.setText(
                "Passen Sie die Zuordnungen der XML-Datei an. Hinzufügen per Haken, "
                "Entfernen durch Abhaken (zugehörige PDFs werden gelöscht):"
            )
            self.ui.alle_xml.setVisible(False)

        # Signale verbinden
        self.ui.selectAllButton.clicked.connect(self.select_all)
        self.ui.deselectAllButton.clicked.connect(self.deselect_all)

        # Baum konfigurieren
        self._setup_tree()

        # Daten laden
        self._load_data()

        # Duplikat-Warnung nur im Edit-Modus (um bestehende Aufrufer nicht bei jeder XML zu stören)
        if self.edit_mode:
            self._warn_on_duplicate_xsl_ids()

    def _setup_tree(self):
        """Konfiguriert das TreeWidget."""
        # Spaltenbreiten setzen
        self.ui.xslNodesTree.setColumnWidth(0, 300)  # XSL-Knoten
        self.ui.xslNodesTree.setColumnWidth(1, 200)  # Details
        self.ui.xslNodesTree.setColumnWidth(2, 100)  # Auswählen
        
        # Header-Eigenschaften
        self.ui.xslNodesTree.header().setStretchLastSection(False)

    def _create_centered_checkbox(self):
        """
        Erstellt eine zentrierte Checkbox in einem Widget.
        
        Returns:
            tuple: (widget, checkbox) - Das Container-Widget und die Checkbox
        """
        # Erstelle Container-Widget
        widget = QWidget()
        
        # Erstelle horizontales Layout
        layout = QHBoxLayout(widget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        # Erstelle Checkbox
        checkbox = QCheckBox()
        checkbox.setChecked(False)
        
        # Füge Checkbox zum Layout hinzu
        layout.addWidget(checkbox)
        
        return widget, checkbox

    def _add_checkboxes_to_tree(self):
        """
        Fügt Checkboxen zu allen XSL-Knoten im Tree hinzu.
        Diese Methode wird nach dem Hinzufügen aller Items aufgerufen.
        """
        try:
            # Durchlaufe alle Items im Tree rekursiv
            root = self.ui.xslNodesTree.invisibleRootItem()
            self._add_checkboxes_recursive(root)
            
            logger.debug(f"Checkboxen zu {len(self.xsl_checkboxes)} XSL-Knoten hinzugefügt")
            
        except Exception as e:
            logger.error(f"Fehler beim Hinzufügen der Checkboxen: {e}")

    def _add_checkboxes_recursive(self, parent_item):
        """
        Fügt rekursiv Checkboxen zu XSL-Knoten hinzu.
        
        Args:
            parent_item: Das Eltern-Item
        """
        for i in range(parent_item.childCount()):
            item = parent_item.child(i)
            
            # Hole das Node-Objekt
            node = item.data(0, Qt.ItemDataRole.UserRole)
            
            if isinstance(node, XslFile):
                # Erstelle zentrierte Checkbox für XSL-Knoten
                checkbox_widget, checkbox = self._create_centered_checkbox()

                # Vorauswahl setzen, wenn ID in preselected_xsl_ids
                if node.id in self.preselected_xsl_ids:
                    checkbox.setChecked(True)
                    self._initial_selected_ids.add(node.id)

                # Setze das Widget in Spalte 2
                self.ui.xslNodesTree.setItemWidget(item, 2, checkbox_widget)

                # Speichere Checkbox- und Node-Referenzen (id() als Key, da XSL-IDs theoretisch doppelt sein können)
                self.xsl_checkboxes[id(node)] = checkbox
                self.xsl_nodes[id(node)] = node

                logger.debug(f"Checkbox für XSL-Knoten '{node.bez}' hinzugefügt")
            
            # Rekursiv für Kinder
            if item.childCount() > 0:
                self._add_checkboxes_recursive(item)

    def _load_data(self):
        """Lädt die Daten in den Dialog."""
        # XML-Datei-Label setzen
        if self.xml_file_path:
            self.ui.xmlFileLabel.setText(f"XML-Datei: {self.xml_file_path.name}")

        # Projekt-Knoten in Baum laden
        self._load_project_nodes()

    def _load_project_nodes(self):
        """Lädt die Projekt-Knoten in das TreeWidget (ohne XML-Knoten).
        Sortiert die Items alphabetisch nach ihrer ID."""
        if not self.project_nodes:
            return

        # TreeWidget leeren
        self.ui.xslNodesTree.clear()
        self.xsl_checkboxes.clear()
        self.xsl_nodes.clear()
        self._initial_selected_ids.clear()

        # Sortiere Root-Nodes alphabetisch nach ID
        sorted_nodes = sorted(self.project_nodes, key=lambda node: node.id)

        # Alle Root-Nodes laden (sortiert)
        for node in sorted_nodes:
            tree_item = self._create_tree_item_from_node(node)
            if tree_item:  # Nur hinzufügen wenn Item erstellt wurde
                self.ui.xslNodesTree.addTopLevelItem(tree_item)

        # Baum expandieren
        self.ui.xslNodesTree.expandAll()
        
        # Checkboxen nach dem Hinzufügen der Items erstellen
        self._add_checkboxes_to_tree()

    def _create_tree_item_from_node(self, node):
        """
        Erstellt ein QTreeWidgetItem aus einem TreeNode oder XslFile.
        XML-Knoten werden ausgeschlossen.
        
        Args:
            node: TreeNode oder XslFile Objekt
            
        Returns:
            QTreeWidgetItem: Das erstellte Tree-Item oder None wenn ausgeschlossen
        """
        try:
            # Erstelle Tree-Item
            item = QTreeWidgetItem()
            
            # Setze die Bezeichnung in Spalte 0
            bez_text = str(node.bez) if node.bez else ""
            item.setText(0, bez_text)
            
            # Speichere das komplette Node-Objekt
            item.setData(0, Qt.ItemDataRole.UserRole, node)
            
            if isinstance(node, TreeNode):
                # TreeNode: Zeige Anzahl der Knoten
                child_count = len(node.children) if node.children else 0
                item.setText(1, f"{child_count} Knoten")
                
                # Lade Knoten rekursiv (sortiert nach ID, nur TreeNode und XslFile, keine XML)
                if node.children:
                    # Filtere und sortiere Kinder
                    valid_children = [child for child in node.children if isinstance(child, (TreeNode, XslFile))]
                    sorted_children = sorted(valid_children, key=lambda child: child.id)
                    
                    for child in sorted_children:
                        child_item = self._create_tree_item_from_node(child)
                        if child_item:
                            item.addChild(child_item)
                        
            elif isinstance(node, XslFile):
                # XslFile: Zeige XSL-Datei-Pfad
                item.setText(1, str(node.xsl_file))
                
                # Checkbox wird später in _add_checkboxes_to_tree() hinzugefügt
                # Keine Knoten für XslFile hinzufügen (XML-Dateien werden ausgeschlossen)
            
            return item
            
        except Exception as e:
            logger.error(f"Fehler beim Erstellen des Tree-Items: {e}")
            return None

    def select_all(self):
        """Wählt alle XSL-Knoten aus."""
        for checkbox in self.xsl_checkboxes.values():
            checkbox.setChecked(True)

    def deselect_all(self):
        """Wählt alle XSL-Knoten ab."""
        for checkbox in self.xsl_checkboxes.values():
            checkbox.setChecked(False)

    def get_selected_xsl_nodes(self):
        """
        Gibt die ausgewählten XSL-Knoten zurück.
        
        Returns:
            list[XslFile]: Liste der ausgewählten XSL-Knoten
        """
        selected_nodes = []
        
        try:
            # Durchlaufe alle XSL-Checkboxes
            for node_id, checkbox in self.xsl_checkboxes.items():
                if checkbox.isChecked():
                    # Finde den entsprechenden XSL-Knoten
                    xsl_node = self._find_xsl_node_by_id(node_id)
                    if xsl_node:
                        selected_nodes.append(xsl_node)
            
            return selected_nodes
            
        except Exception as e:
            logger.error(f"Fehler beim Sammeln der ausgewählten XSL-Knoten: {e}")
            return []

    def _find_xsl_node_by_id(self, node_id):
        """
        Findet einen XSL-Knoten anhand seiner ID.
        
        Args:
            node_id: Die ID des Knotens (Python id())
            
        Returns:
            XslFile: Der gefundene XSL-Knoten oder None
        """
        return self._find_xsl_node_recursive(self.project_nodes, node_id)

    def _find_xsl_node_recursive(self, nodes, target_id):
        """
        Sucht rekursiv nach einem XSL-Knoten mit der angegebenen ID.
        
        Args:
            nodes: Liste der Nodes zum Durchsuchen
            target_id: Die zu suchende ID
            
        Returns:
            XslFile: Der gefundene XSL-Knoten oder None
        """
        for node in nodes:
            if isinstance(node, XslFile) and id(node) == target_id:
                return node
            
            # Rekursiv in Knoten suchen (nur bei TreeNode)
            if isinstance(node, TreeNode) and node.children:
                found = self._find_xsl_node_recursive(node.children, target_id)
                if found:
                    return found
        
        return None

    def get_xml_file_path(self):
        """
        Gibt den Pfad zur XML-Datei zurück.

        Returns:
            Path: Pfad zur XML-Datei
        """
        return self.xml_file_path

    def is_apply_to_all(self):
        """
        Prüft, ob die Checkbox 'Alle XML-Dateien' aktiviert ist.

        Returns:
            bool: True wenn die Checkbox aktiviert ist, sonst False
        """
        return self.ui.alle_xml.isChecked()

    def get_selection_diff(self) -> tuple[list, list]:
        """
        Gibt die Änderungen der Auswahl gegenüber der Vorauswahl zurück.
        Nützlich im Edit-Modus, um nur die tatsächlich geänderten Zuordnungen zu verarbeiten.

        Returns:
            tuple[list[XslFile], list[XslFile]]: (added_nodes, removed_nodes)
                - added_nodes: XslFile-Objekte, die neu angehakt wurden
                - removed_nodes: XslFile-Objekte, deren Haken entfernt wurde
        """
        added_nodes = []
        removed_nodes = []

        for py_id, checkbox in self.xsl_checkboxes.items():
            node = self.xsl_nodes.get(py_id)
            if node is None:
                continue

            was_selected = node.id in self._initial_selected_ids
            is_selected = checkbox.isChecked()

            if is_selected and not was_selected:
                added_nodes.append(node)
            elif not is_selected and was_selected:
                removed_nodes.append(node)

        return added_nodes, removed_nodes

    def _warn_on_duplicate_xsl_ids(self):
        """
        Zeigt eine Warnung, wenn im Projekt mehrere XslFile-Instanzen mit identischer ID existieren.
        Die ID soll eindeutig sein - Duplikate weisen auf einen Datenfehler hin.
        """
        id_to_nodes: dict = {}
        for py_id, node in self.xsl_nodes.items():
            id_to_nodes.setdefault(node.id, []).append(node)

        duplicates = {xsl_id: nodes for xsl_id, nodes in id_to_nodes.items() if len(nodes) > 1}
        if not duplicates:
            return

        dup_lines = [f"  - ID {xsl_id}: {len(nodes)}× ({', '.join(n.bez for n in nodes)})" for xsl_id, nodes in duplicates.items()]
        logger.warning(f"Doppelte XSL-IDs im Projekt gefunden:\n{chr(10).join(dup_lines)}")
        QMessageBox.warning(
            self,
            "Doppelte XSL-IDs",
            "Im Projekt existieren XSL-Knoten mit identischer ID. "
            "Die IDs sollten eindeutig sein:\n\n" + "\n".join(dup_lines),
        )

    def accept(self):
        """Überschreibt accept() um Validierung durchzuführen."""
        # Im Edit-Modus: 0 Auswahlen erlauben (bedeutet: XML überall entfernen)
        if self.edit_mode:
            super().accept()
            return

        selected_nodes = self.get_selected_xsl_nodes()
        if not selected_nodes:
            QMessageBox.warning(self, "Warnung", "Bitte wählen Sie mindestens einen XSL-Knoten aus.")
            return

        super().accept()
