import datetime
import logging
from collections import namedtuple
from typing import List
import yaml
from qgis.PyQt.QtWidgets import QVBoxLayout
from qgis.PyQt import QtCore, QtWidgets, QtGui
from qgis.PyQt.QtCore import Qt, QSettings
from qgis.PyQt.QtWidgets import QFrame, QSpacerItem, QSizePolicy, QGridLayout, QGroupBox
from sqlalchemy import null
from sqlalchemy.orm import MapperProperty, RelationshipProperty
from SAGisXPlanung import Session
from SAGisXPlanung.XPlan.types import InvalidFormException
from SAGisXPlanung.config import xplan_tooltip, export_version
from SAGisXPlanung.core.helper.sqlalchemy import is_mapped
from SAGisXPlanung.gui.widgets.inputs.QRelationDropdowns import QAddRelationDropdown
from SAGisXPlanung.gui.widgets.inputs.input_widgets import QFileInput, QMultiInputWidget
from SAGisXPlanung.gui.widgets.inputs.base_input_element import BaseInputElement
PYQT_DEFAULT_DATE = datetime.date(1752, 9, 14)
logger = logging.getLogger(__name__)

class DataInputPage(QtWidgets.QScrollArea):
    addRelationRequested = QtCore.pyqtSignal(object, bool, str)
    parentLinkClicked = QtCore.pyqtSignal()
    requestPageToTop = QtCore.pyqtSignal()

    def __init__(self, cls_type, parent_class, parent_attribute=None, existing_xid=None, *args, **kwargs):
        super(DataInputPage, self).__init__(*args, **kwargs)
        self.cls_type = cls_type
        self.parent_class = parent_class
        self.parent_attribute = parent_attribute
        self.existing_xid = existing_xid
        self.child_pages = []
        self.fields = {}
        self.required_inputs = []
        s = QSettings()
        self.ATTRIBUTE_CONFIG = yaml.safe_load(s.value(f'plugins/xplanung/attribute_config', '')) or {}
        self.setFrameShape(QFrame.Shape.NoFrame)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.horizontalScrollBar().setEnabled(False)
        self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
        self.content_widget = QtWidgets.QWidget()
        self.setWidget(self.content_widget)
        self.vBox = QtWidgets.QVBoxLayout()
        self.vBox.setSpacing(20)
        self.content_widget.setLayout(self.vBox)
        if self.parent_class is not None:
            self.createHeader()
        self.createLayout()
        spacer = QtWidgets.QWidget()
        spacer.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
        self.vBox.addWidget(spacer)

    def sizeHint(self):
        size = super(DataInputPage, self).sizeHint()
        size.setWidth(size.width() + 5 * self.verticalScrollBar().sizeHint().width())
        return size

    def addChildPage(self, page):
        self.child_pages.append(page)

    def removeChildPage(self, page):
        self.child_pages.remove(page)

    def createHeader(self):
        widget = QtWidgets.QWidget(self)
        widget.setStyleSheet('\n            QLabel#lParentAttribute {\n                color: #64748b;\n            }\n            QPushButton#lParentClass{\n                color: #1e293b;\n                background: palette(window);\n                border: 0px;\n            }\n            QPushButton#lParentClass:hover {\n                color: #0ea5e9;\n            }\n        ')
        h_layout = QtWidgets.QHBoxLayout(widget)
        h_layout.setContentsMargins(0, 10, 0, 10)
        l1 = QtWidgets.QLabel('Objekt gehört zu:')
        l2 = QtWidgets.QPushButton(self.parent_class.__name__, objectName='lParentClass')
        l1.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred)
        l2.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred)
        h_layout.addWidget(l1)
        h_layout.addWidget(l2)
        if self.parent_attribute:
            label_display_name = self.parent_class.xplan_attribute_name(self.parent_attribute)
            l3 = QtWidgets.QLabel(f'(Attribut: {label_display_name})', objectName='lParentAttribute')
            l2.setCursor(Qt.CursorShape.PointingHandCursor)
            l2.clicked.connect(lambda checked: self.parentLinkClicked.emit())
            h_layout.addWidget(l3)
        else:
            l2.setDisabled(True)
            h_layout.addItem(QSpacerItem(20, 10, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
        self.vBox.addWidget(widget)

    def createLayout(self):
        column_resizer = ColumnResizer()
        for cls in self.cls_type.mro():
            if not is_mapped(cls):
                continue
            group_box = QGroupBox(cls.__name__)
            grid = QtWidgets.QGridLayout(group_box)
            col_skip = 0
            for i, (key, prop) in enumerate(cls.element_order(include_base=False, export=False, ret_fmt='sqla', version=export_version())):
                if key in self.ATTRIBUTE_CONFIG.get(cls.__name__, []):
                    col_skip += 1
                    continue
                label, control = self.create_input(key, prop)
                control.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
                if isinstance(control, QMultiInputWidget):
                    spacer_container = QVBoxLayout()
                    spacer_container.addItem(QSpacerItem(20, 5, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum))
                    spacer_container.addWidget(label)
                    spacer_container.addItem(QSpacerItem(20, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding))
                    grid.addLayout(spacer_container, i - col_skip, 0)
                else:
                    grid.addWidget(label, i - col_skip, 0)
                grid.addWidget(control, i - col_skip, 1)
                self.fields[key] = control
            if grid.count() == 0:
                continue
            column_resizer.form_widgets.append(grid)
            self.vBox.addWidget(group_box)
        column_resizer.applyResize()

    def onRelationButtonClicked(self, class_type, uselist, parent_attribute):
        if not uselist:
            button = self.fields[parent_attribute]
            button.setDisabled(True)
        self.addRelationRequested.emit(class_type, uselist, parent_attribute)

    def create_input(self, attr_name: str, mapper_property: MapperProperty):
        cls = mapper_property.parent.class_
        if isinstance(mapper_property, RelationshipProperty):
            form_type = mapper_property.info.get('form-type')
            label_display_name, tooltip = cls.relation_prop_display((attr_name, mapper_property))
            label = QtWidgets.QLabel(label_display_name)
            label.setObjectName(attr_name)
            if tooltip:
                label.setToolTip(tooltip)
            if mapper_property.info.get('nullable') is False:
                label.setStyleSheet('font-weight: bold')
            if form_type == 'inline':
                stub = namedtuple('stub', ['cls_type'])
                control = QAddRelationDropdown(stub(cls_type=self.cls_type), (attr_name, mapper_property), parent=self)
                return (label, control)
            else:
                widget = QtWidgets.QPushButton('Hinzufügen')
                widget.clicked.connect(lambda state, c=mapper_property.mapper.class_, u=mapper_property.uselist, a=attr_name: self.onRelationButtonClicked(c, u, a))
                return (label, widget)
        column = getattr(cls, attr_name).property.columns[0]
        field_type = column.type
        nullable = column.nullable
        control = BaseInputElement.create(field_type, self)
        xplan_name = cls.xplan_attribute_name(attr_name)
        if column.doc:
            label = QtWidgets.QLabel(column.doc)
            label.setToolTip(f'XPlanung-Attribut: {xplan_name}')
        else:
            label = QtWidgets.QLabel(xplan_name)
            tooltip = xplan_tooltip(cls, xplan_name)
            label.setToolTip(tooltip)
        label.setObjectName(attr_name)
        if not nullable:
            font = QtGui.QFont()
            font.setBold(True)
            label.setFont(font)
            self.required_inputs.append(attr_name)
        return (label, control)

    def getObjectFromInputs(self, validate_forms=True):
        if validate_forms and (not self.validateForms()):
            raise InvalidFormException()
        obj = self.cls_type()
        if self.existing_xid:
            obj.id = self.existing_xid
        for column, input_field in self.fields.items():
            if isinstance(input_field, QAddRelationDropdown):
                class_type = getattr(obj.__class__, column).property.mapper.class_
                if input_field.relation[1].secondary is None:
                    selected_object = input_field.value()
                    if not selected_object:
                        setattr(obj, f'{column}_id', null())
                        continue
                    with Session.begin() as session:
                        obj_from_db = session.merge(selected_object)
                        setattr(obj, f'{column}_id', obj_from_db.id)
                    setattr(obj, column, obj_from_db)
                else:
                    selected_objects = input_field.value()
                    with Session.begin() as session:
                        obj_list = []
                        for selected_item in input_field.value():
                            obj_list.append(session.merge(selected_item))
                    setattr(obj, column, obj_list)
                continue
            if not isinstance(input_field, BaseInputElement):
                continue
            setattr(obj, column, input_field.value())
            if isinstance(input_field, QFileInput):
                obj.set_file_data(column, input_field.file())
        return obj

    def validateForms(self):
        is_valid = True
        invalid_attributes = []
        for attribute, control in self.fields.items():
            if not isinstance(control, BaseInputElement):
                continue
            required_input = bool(attribute in self.required_inputs)
            if not control.validate_widget(required_input):
                is_valid = False
                control.setInvalid(True)
                invalid_attributes.append(attribute)
        if not is_valid:
            label = self.findChild(QtWidgets.QLabel, invalid_attributes[0])
            self.ensureWidgetVisible(label)
            self.requestPageToTop.emit()
        return is_valid

class ColumnResizer:
    """
    Helper-Klasse zum Anpassen des Layouts der Eingabeformulare.
    Gleicht die Breite der Spalten mehrerer voneinander unabhängiger GridLayouts an.

    Inspiriert durch https://github.com/agateau/columnresizer
    """

    def __init__(self):
        self.form_widgets: List[QGridLayout] = []

    def applyResize(self):
        largest_size = 0
        for widget in self.form_widgets:
            if widget.rowCount() < 1:
                continue
            for i in range(widget.rowCount()):
                label = widget.itemAtPosition(i, 0).widget()
                if not label:
                    continue
                size = label.minimumSizeHint().width()
                largest_size = max(largest_size, size)
        for widget in self.form_widgets:
            if widget.rowCount() < 1:
                continue
            for i in range(widget.rowCount()):
                label = widget.itemAtPosition(i, 0).widget()
                if not label:
                    continue
                label.setMinimumWidth(largest_size)