from qgis.PyQt import QtCore
from qgis.PyQt.QtCore import QVariant
from qgis.PyQt.QtGui import QColor, QFont, QStandardItemModel

from openlog.datamodel.assay.generic_assay import AssaySeriesType
from openlog.gui.utils.column_definition import ColumnDefinition


class ColumnDefinitionTableModel(QStandardItemModel):
    COLUMN_ROW = 0
    MAPPING_ROW = 1
    UNIT_ROW = 2
    SERIES_TYPE_ROW = 3

    FIXED_ROLE = QtCore.Qt.UserRole
    OPTIONAL_ROLE = QtCore.Qt.UserRole + 1

    def __init__(self, parent=None) -> None:
        """
        QStandardItemModel for column definition display

        Args:
            parent: QWidget
        """
        super().__init__(parent)
        self.setVerticalHeaderLabels(
            [self.tr("Column"), self.tr("Map"), self.tr("Unit"), self.tr("Type")]
        )

    def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
        """
        Override QStandardItemModel flags to remove edition for expected columns

        Args:
            index: QModelIndex

        Returns: index flags

        """
        flags = super().flags(index)
        if (
            index.row() == self.COLUMN_ROW
            or index.row() == self.UNIT_ROW
            or index.row() == self.SERIES_TYPE_ROW
        ):
            fixed = super().data(
                self.index(self.COLUMN_ROW, index.column()), self.FIXED_ROLE
            )
            series_type = AssaySeriesType[
                super().data(self.index(self.SERIES_TYPE_ROW, index.column()))
            ]
            fixed |= index.row() == self.UNIT_ROW and (
                series_type == AssaySeriesType.CATEGORICAL
                or series_type == AssaySeriesType.DATETIME
                or series_type == AssaySeriesType.NOMINAL
                or series_type == AssaySeriesType.IMAGERY
                or series_type == AssaySeriesType.SPHERICAL
            )

            if index.row() == self.UNIT_ROW and series_type == AssaySeriesType.POLAR:
                fixed = True

            if (
                index.row() == self.MAPPING_ROW
                and series_type == AssaySeriesType.SPHERICAL
            ):
                fixed = True

            if fixed:
                flags = flags & QtCore.Qt.ItemIsEditable
        return flags

    def data(
        self, index: QtCore.QModelIndex, role: int = QtCore.Qt.DisplayRole
    ) -> QVariant:
        """
        Override QStandardItemModel data() for font of expected column

        Args:
            index: QModelIndex
            role: Qt role

        Returns: QVariant

        """
        result = super().data(index, role)
        if role == QtCore.Qt.FontRole and (
            index.row() == self.COLUMN_ROW
            or index.row() == self.UNIT_ROW
            or index.row() == self.SERIES_TYPE_ROW
        ):
            fixed = super().data(
                self.index(self.COLUMN_ROW, index.column()), self.FIXED_ROLE
            )
            if fixed:
                result = QFont()
                result.setBold(True)

        series_type = AssaySeriesType[
            super().data(self.index(self.SERIES_TYPE_ROW, index.column()))
        ]
        if (
            role == QtCore.Qt.DisplayRole
            and index.row() == self.UNIT_ROW
            and (
                series_type == AssaySeriesType.CATEGORICAL
                or series_type == AssaySeriesType.DATETIME
                or series_type == AssaySeriesType.NOMINAL
                or series_type == AssaySeriesType.IMAGERY
                or series_type == AssaySeriesType.SPHERICAL
            )
        ):
            result = ""

        if (
            role == QtCore.Qt.DisplayRole
            and index.row() == self.UNIT_ROW
            and series_type == AssaySeriesType.POLAR
        ):
            result = "°"

        if (
            role == QtCore.Qt.DisplayRole
            and index.row() == self.MAPPING_ROW
            and series_type == AssaySeriesType.SPHERICAL
        ):
            result = ""

        if role == QtCore.Qt.DisplayRole and index.row() == self.COLUMN_ROW:
            used_lower_column_name = {}
            for c in range(0, self.columnCount()):
                # Use column name in lower because SQLite is not case sensitive
                col_lower = str(super().data(self.index(index.row(), c))).lower()
                col_type = AssaySeriesType[
                    super().data(self.index(self.SERIES_TYPE_ROW, c))
                ]
                # If no column defined, use mapping
                if not col_lower:
                    if col_type == AssaySeriesType.SPHERICAL:
                        col_lower = "spherical"
                    else:
                        col_lower = str(
                            super().data(self.index(self.MAPPING_ROW, c))
                        ).lower()

                if col_lower in used_lower_column_name:
                    used_lower_column_name[col_lower].append(c)
                else:
                    used_lower_column_name[col_lower] = [c]

            # If no column defined, use mapping
            if not result:
                if series_type == AssaySeriesType.SPHERICAL:
                    result = "spherical"
                else:
                    result = str(
                        super().data(self.index(self.MAPPING_ROW, index.column()))
                    )

            # Check if duplicated column used
            col = result
            col_lower = col.lower()
            if col and len(used_lower_column_name[col_lower]) > 1:
                index_col = used_lower_column_name[col_lower].index(index.column())
                if index_col:
                    result = f"{col}_{index_col}"

        return result

    def add_column_definition(self, column_mapping: ColumnDefinition) -> None:
        """
        Add a row in QStandardItemModel

        Args:
            column_mapping:
        """
        self.insertColumn(self.columnCount())
        col = self.columnCount() - 1
        self._set_column_definition(col, column_mapping)

    def remove_column(self, column: str) -> None:
        """
        Remove a column mapping

        Args:
            column: (str) column name

        """
        for i in range(self.columnCount()):
            column_row = str(self.data(self.index(self.COLUMN_ROW, i)))
            if column_row == column:
                self.removeColumn(i)
                return

    def set_column_definition(self, columns: [ColumnDefinition]) -> None:
        """
        Define expected column and remove not used column

        Args:
            columns: expected column list
        """
        columns_name = [x.column for x in columns]
        for i in reversed(range(self.columnCount())):
            expected_col = str(self.data(self.index(self.COLUMN_ROW, i)))
            if expected_col not in columns_name:
                self.removeColumn(i)

        for col in columns:
            col_index = self._get_column_col_index(col.column)
            if col_index != -1:
                self._set_column_definition(col_index, col)
            else:
                self.add_column_definition(col)

    def set_column_mapping(self, column: str, mapping: str) -> None:
        col_index = self._get_column_col_index(column)
        if col_index != -1:
            self.setData(self.index(self.MAPPING_ROW, col_index), mapping)

    def get_column_definition(
        self, only_with_mapping: bool = False
    ) -> [ColumnDefinition]:
        """
        Return list of columns definition. Use only_with_mapping option to get only columns with defined mapping

        Args:
            only_with_mapping: True for expected columns only with mapping, False for all expected columns

        Returns: [ColumnDefinition]

        """
        result = []
        for i in range(self.columnCount()):
            expected_col = str(self.data(self.index(self.COLUMN_ROW, i)))
            if expected_col:
                column = ColumnDefinition(
                    column=str(self.data(self.index(self.COLUMN_ROW, i))),
                    mapping=str(self.data(self.index(self.MAPPING_ROW, i))),
                    unit=str(self.data(self.index(self.UNIT_ROW, i))),
                    fixed=self.data(self.index(self.COLUMN_ROW, i), self.FIXED_ROLE),
                    optional=self.data(
                        self.index(self.COLUMN_ROW, i), self.OPTIONAL_ROLE
                    ),
                    series_type=AssaySeriesType[
                        str(self.data(self.index(self.SERIES_TYPE_ROW, i)))
                    ],
                )

                if only_with_mapping:
                    mapping = self.data(self.index(self.MAPPING_ROW, i))
                    if mapping:
                        result.append(column)
                else:
                    result.append(column)
        return result

    def set_column_color_and_tooltip(
        self, column: str, tooltip: str, color: QColor
    ) -> None:
        """
        Set color and tooltip for an expected column

        Args:
            column: (str) expected column
            tooltip:  (str) tooltip
            color: (QColor) foreground color
        """
        for i in range(self.columnCount()):
            if self.data(self.index(self.COLUMN_ROW, i)) == column:
                self.setData(
                    self.index(self.COLUMN_ROW, i), tooltip, QtCore.Qt.ToolTipRole
                )
                self.setData(
                    self.index(self.COLUMN_ROW, i), color, QtCore.Qt.ForegroundRole
                )

    def _set_column_definition(
        self, col: int, column_mapping: ColumnDefinition
    ) -> None:
        self.setData(self.index(self.COLUMN_ROW, col), column_mapping.column)
        # Add fixed data in COLUMN_ROW UserRole
        self.setData(
            self.index(self.COLUMN_ROW, col),
            column_mapping.fixed,
            self.FIXED_ROLE,
        )

        # Add optional data in COLUMN_ROW UserRole
        self.setData(
            self.index(self.COLUMN_ROW, col),
            column_mapping.optional,
            self.OPTIONAL_ROLE,
        )

        self.setData(self.index(self.MAPPING_ROW, col), column_mapping.mapping)
        self.setData(self.index(self.UNIT_ROW, col), column_mapping.unit)
        self.setData(
            self.index(self.SERIES_TYPE_ROW, col), column_mapping.series_type.name
        )

    def _get_column_col_index(self, col: str) -> int:
        """
        Get expected column column index (-1 if expected column not available)

        Args:
            col: (str) expected column

        Returns: expected column column index (-1 if expected column not available)

        """
        column = -1
        for i in range(self.columnCount()):
            val = str(self.data(self.index(self.COLUMN_ROW, i)))
            if val == col:
                column = i
        return column

    def _get_or_create_column(self, col: str) -> int:
        """
        Get or create column

        Args:
            col: expected column name

        Returns: column index

        """
        column = self._get_column_col_index(col)
        if column == -1:
            self.add_column_definition(ColumnDefinition(column=col))
            column = self.columnCount() - 1
        return column
