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


class CategoryPolicy:
    def __init__(
        self, column: str = None, category: str = None, validation: str = None
    ) -> None:

        self.column = column
        self.category = category
        self.validation = validation


class AssayColumnCategoryTableModel(QStandardItemModel):
    COLUMN_COL = 0
    CATEGORIE_COLUMN = 1
    VALIDATION_COLUMN = 2

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

        Args:
            parent: QWidget
        """
        super().__init__(parent)
        self.setHorizontalHeaderLabels(
            [
                "Column",
                "Category",
                "Validation",
            ]
        )

        self.fixed_columns = []

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

        Args:
            index: QModelIndex

        Returns: index flags

        """
        flags = super().flags(index)
        if index.column() == self.COLUMN_COL or (
            index.column() == self.CATEGORIE_COLUMN
            and self.data(self.index(index.row(), self.COLUMN_COL))
            in self.fixed_columns
        ):
            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.column() == self.COLUMN_COL:
            result = QFont()
            result.setBold(True)

        return result

    def add_assay_column_category(
        self, column: str, category: str, validation: str
    ) -> None:
        """
        Add a row in QStandardItemModel

        Args:
            column : (str) column name
            category: (str) column category
        """
        self.insertRow(self.rowCount())
        row = self.rowCount() - 1
        self._set_assay_column_category(row, column, category, validation)

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

        Args:
            column: (str) column name

        """
        for i in range(self.rowCount()):
            column_row = str(self.data(self.index(i, self.COLUMN_COL)))
            if column_row == column:
                self.removeRow(i)
                return

    def set_assay_column_categories(
        self, column_categories: list[CategoryPolicy]
    ) -> None:
        """
        Define expected column category and remove not used column

        Args:
            column_categories: {str: str} dict of category associated to column
        """
        columns_name = [obj.column for obj in column_categories]
        for i in reversed(range(self.rowCount())):
            expected_col = str(self.data(self.index(i, self.COLUMN_COL)))
            if expected_col not in columns_name:
                self.removeRow(i)

        for obj in column_categories:
            row_index = self._get_column_row_index(obj.column)
            if row_index != -1:
                self._set_assay_column_category(
                    row_index, obj.column, obj.category, obj.validation
                )
            else:
                self.add_assay_column_category(obj.column, obj.category, obj.validation)

    def get_assay_column_categories(self) -> {str: str}:
        """
        Return dict of expected column category

        Returns: {str: str}

        """
        result = {}
        for i in range(self.rowCount()):
            expected_col = str(self.data(self.index(i, self.COLUMN_COL)))
            if expected_col:
                name = self.data(self.index(i, self.CATEGORIE_COLUMN))
                result[expected_col] = name
        return result

    def get_assay_column_category_validation(self, column: str) -> str:
        """
        Get category validation option for a column

        Args:
            column: (str) column name

        Returns: (str) category validation

        """
        row = self._get_column_row_index(column)
        if row != -1:
            result = str(self.data(self.index(row, self.VALIDATION_COLUMN)))
        else:
            result = ""
        return result

    def _set_assay_column_category(
        self, row: int, column: str, category: str, validation: str
    ) -> None:
        self.setData(self.index(row, self.COLUMN_COL), column)

        self.setData(
            self.index(row, self.CATEGORIE_COLUMN), category, QtCore.Qt.EditRole
        )

        self.setData(
            self.index(row, self.VALIDATION_COLUMN), validation, QtCore.Qt.EditRole
        )

    def _get_column_row_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)

        """
        row = -1
        for i in range(self.rowCount()):
            val = str(self.data(self.index(i, self.COLUMN_COL)))
            if val == col:
                row = i
        return row

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

        Args:
            col: expected column name

        Returns: column index

        """
        row = self._get_column_row_index(col)
        if row == -1:
            self.add_assay_column_category(column=col, category="")
            row = self.rowCount() - 1
        return row
