import json
import os
from pathlib import Path
from typing import Union

from qgis.PyQt import QtCore, uic
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtWidgets import (
    QColorDialog,
    QDialog,
    QFileDialog,
    QHeaderView,
    QMessageBox,
    QStyledItemDelegate,
    QWidget,
)

from openlog.datamodel.assay.generic_assay import GenericAssay
from openlog.gui.assay_visualization.categorical_symbology import CategoricalSymbology
from openlog.gui.assay_visualization.extended.bar_symbology_color_table_model import (
    BarSymbologyColorTableModel,
)


class HtmlColorItemDelegate(QStyledItemDelegate):
    def __init__(self, parent) -> None:
        """
        QStyledItemDelegate for HTML color string definition

        Args:
            parent:
        """
        super().__init__(parent)

    def setEditorData(self, editor: QWidget, index: QtCore.QModelIndex) -> None:
        editor.setCurrentColor(QColor(index.data(QtCore.Qt.DisplayRole)))

    def createEditor(self, parent, option, index) -> QWidget:
        """
        Create a QColorDialog for color definition

        Args:
            parent: QWidget
            option: QStyleOptionViewItem
            index: QModelIndex

        Returns: QDoubleSpinBox

        """
        editor = QColorDialog(parent)
        editor.setOption(QColorDialog.DontUseNativeDialog, True)
        editor.setModal(True)
        return editor

    def setModelData(
        self,
        editor: QWidget,
        model: QtCore.QAbstractItemModel,
        index: QtCore.QModelIndex,
    ) -> None:
        if editor.result() == QDialog.Accepted:
            model.setData(index, editor.selectedColor().name())


STRING_FILTER = "JSON file(*.json)"
STRING_SELECT = "Select file"


class CategoricalSymbologyDialog(QDialog):
    """
    Base class dialog to display categorical symbology.
    Default is one tab for color configuration.

    Args:
        parent: QDialog parent
    """

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.symbology_class = CategoricalSymbology
        uic.loadUi(
            os.path.join(os.path.dirname(__file__), "point_symbology_dialog.ui"), self
        )
        self.setWindowTitle(self.tr("Categorical symbology"))

        self._values_updated = False

        self.assay = None
        self._color_map_model = BarSymbologyColorTableModel()
        self._color_map_model.dataChanged.connect(self._model_data_changed)
        self.color_table_view.setModel(self._color_map_model)
        self.color_table_view.setItemDelegateForColumn(
            self._color_map_model.VALUE_COL, HtmlColorItemDelegate(self)
        )
        self.color_table_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch
        )

        self.save_button.clicked.connect(self.save_symbology)
        self.load_button.clicked.connect(self.load_symbology)
        self.merge_button.clicked.connect(self.merge_symbology)

    def set_assay(self, assay: GenericAssay) -> None:
        self.assay = assay
        self._define_col_cbx(assay)

    def _define_col_cbx(self, assay: GenericAssay) -> None:
        if assay:
            available_columns = [
                col.name for col in assay.assay_definition.columns.values()
            ]
        else:
            available_columns = []

        self.color_key_col_cbx.clear()
        self.color_key_col_cbx.addItems(available_columns)

    def _get_symbology_from_assay(self) -> Union[CategoricalSymbology, None]:
        if self.assay:
            symbology = self.get_symbology()
            symbology.init_from_assay(self.assay)
            return symbology
        else:
            return None

    def _color_key_column_changed(self) -> None:
        assay_symbology = self._get_symbology_from_assay()
        if assay_symbology:
            current_symbology = self.get_symbology()
            current_symbology.color_map = assay_symbology.color_map
            self.set_symbology(current_symbology)

    def accept(self) -> None:
        """
        Check for changes and ask for save

        """
        if self._values_updated:
            ret = QMessageBox.question(
                self,
                self.tr("Save symbology"),
                self.tr(
                    "You have made changes to your symbology, do you wish to save them to a file?"
                ),
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                QMessageBox.Yes,
            )
            if ret == QMessageBox.No or (
                ret == QMessageBox.Yes and self.save_symbology()
            ):
                super().accept()
        else:
            super().accept()

    def _model_data_changed(self) -> None:
        self._values_updated = True

    def set_symbology(self, symbology: CategoricalSymbology) -> None:

        self.color_key_col_cbx.blockSignals(True)

        self.color_key_col_cbx.setCurrentText(symbology.color_col)

        self.color_key_col_cbx.blockSignals(False)

        self._color_map_model.set_string_map(symbology.color_map)

        self._values_updated = False

    def get_symbology(self) -> CategoricalSymbology:

        """
        Returns displayed bar symbology

        Returns: (BarSymbology)

        """
        result = CategoricalSymbology()

        result.color_col = self.color_key_col_cbx.currentText()
        result.color_map = self._color_map_model.get_string_map()

        return result

    def save_symbology(self) -> bool:
        """
        Save symbology in a JSON file selected by user

        Returns: (bool) True if symbology was saved by user, False otherwise

        """
        saved = False
        filename, _ = QFileDialog.getSaveFileName(
            self, self.tr(STRING_SELECT), "bar_symbology.json", STRING_FILTER
        )
        if filename:
            symbology = self.get_symbology()
            with open(filename, "w") as f:
                f.write(symbology.to_json())
            self._values_updated = False
            saved = True

        return saved

    def merge_symbology(self) -> None:
        """
        Merge symbology from a JSON file selected by user with current configuration

        """
        filename, _ = QFileDialog.getOpenFileName(
            self, self.tr(STRING_SELECT), "", STRING_FILTER
        )
        if filename:
            with open(filename, "r") as f:
                current_symbology = self.get_symbology()
                current_symbology.merge(
                    self.symbology_class.from_json(json.load(f)), replace=True
                )
                self.set_symbology(current_symbology)
            self._values_updated = False

    def load_symbology(self) -> None:
        """
        Load symbology from a JSON file selected by user

        """
        filename, _ = QFileDialog.getOpenFileName(
            self, self.tr(STRING_SELECT), "", STRING_FILTER
        )
        if filename:
            with open(filename, "r") as f:
                new_symbology = self.symbology_class.from_json(json.load(f))
                self.set_symbology(new_symbology)
            self._values_updated = False
