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

from openlog.datamodel.assay.detection_limit import AssayColumnDetectionLimit


class AssayColumnDetectionLimitModel(QStandardItemModel):
    COLUMN_COL = 0
    MIN_DETECTION = 1
    MAX_DETECTION = 2

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

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

        self.setHorizontalHeaderLabels(["Column", "Lower limit", "Upper limit"])

    def set_cell_uneditable(self, row: int, column: int) -> None:
        """
        Set ItemIsEditable flag to a cell.
        """

        self.item(row, column).setFlags(QtCore.Qt.ItemIsEditable)

    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:
            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_detection_limit(
        self, column: str, detection_limit: AssayColumnDetectionLimit
    ) -> None:
        """
        Add a row in QStandardItemModel

        Args:
            column : (str) column name
            detection_limit: AssayColumnDetectionLimit
        """
        self.insertRow(self.rowCount())
        row = self.rowCount() - 1
        self._set_assay_column_detection_limit(row, column, detection_limit)

    def remove_column(self, column: str) -> None:
        """
        Remove a column uncertainty 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_detection_limit(
        self, detection_limit: {str: AssayColumnDetectionLimit}
    ) -> None:
        """
        Define expected column and remove not used column

        Args:
            detection_limit: dict
        """
        columns_name = detection_limit.keys()
        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 col, detection in detection_limit.items():
            row_index = self._get_column_row_index(col)
            if row_index != -1:
                self._set_assay_column_detection_limit(row_index, col, detection)
            else:
                self.add_assay_column_detection_limit(col, detection)

    def get_assay_column_detection_limit(self) -> {str: AssayColumnDetectionLimit}:
        """
        Return dict of AssayColumnDetectionLimit

        Returns: {str: AssayColumnDetectionLimit}

        """
        result = {}
        for i in range(self.rowCount()):
            expected_col = str(self.data(self.index(i, self.COLUMN_COL)))
            if expected_col:
                min_detection = self.data(self.index(i, self.MIN_DETECTION))
                max_detection = self.data(self.index(i, self.MAX_DETECTION))
                column = AssayColumnDetectionLimit(
                    detection_min_col=min_detection, detection_max_col=max_detection
                )
                result[expected_col] = column
        return result

    def _set_assay_column_detection_limit(
        self, row: int, column: str, detection_limit: AssayColumnDetectionLimit
    ) -> None:
        self.setData(self.index(row, self.COLUMN_COL), column)

        self.setData(
            self.index(row, self.MIN_DETECTION),
            detection_limit.detection_min_col,
        )
        self.setData(
            self.index(row, self.MAX_DETECTION),
            detection_limit.detection_max_col,
        )

    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_detection_limit(
                column=col, uncertainty=AssayColumnDetectionLimit()
            )
            row = self.rowCount() - 1
        return row
