# Copyright 2021 Vincent Texier <vit@free.fr>
#
# This software is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import logging
from datetime import datetime, timedelta, timezone
from typing import List, Optional

from PyQt5.QtCore import (
    QAbstractItemModel,
    QAbstractListModel,
    QLocale,
    QModelIndex,
    QSize,
    Qt,
    QVariant,
)
from PyQt5.QtGui import QPainter, QPalette
from PyQt5.QtWidgets import QStyle, QStyledItemDelegate, QStyleOptionViewItem

from tikka.domains.application import Application
from tikka.domains.entities.identity import Certification, IdentityStatus
from tikka.interfaces.adapters.repository.certifications import (
    CertificationsRepositoryInterface,
)
from tikka.slots.pyqt.entities.constants import (
    NUMERIC_DISPLAY_COLOR_GREEN,
    NUMERIC_DISPLAY_COLOR_ORANGE,
    NUMERIC_DISPLAY_COLOR_RED,
)
from tikka.slots.pyqt.widgets.account_certification_row import (
    AccountCertificationRowWidget,
)


class CertificationItemDelegate(QStyledItemDelegate):
    """
    CertificationItemDelegate class
    """

    def __init__(self):
        """
        Init CertificationItemDelegate instance
        """
        super().__init__()

        # set item height >= AccountCertificationRowWidget.height
        self.item_height = 71

    def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex):
        """
        Return item size

        Force height

        :param option: QStyleOptionViewItem instance
        :param index: QModelIndex instance
        :return:
        """
        return QSize(option.rect.width(), self.item_height)

    def paint(
        self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
    ) -> None:
        """
        Draw certification row item in listview

        :param painter: QPainter instance
        :param option: QStyleOptionViewItem instance
        :param index: QModelIndex instance
        :return:
        """
        # Init style option
        style_option = QStyleOptionViewItem(option)
        self.initStyleOption(style_option, index)

        account_address_label = index.data(
            CertificationsListModel.AccountAddressLabelRole
        )
        account_name_label = index.data(CertificationsListModel.AccountNameRole)
        identity_name_label = index.data(CertificationsListModel.IdentityNameRole)
        identity_status = index.data(CertificationsListModel.IdentityStatusRole)
        identity_status_color = index.data(
            CertificationsListModel.IdentityStatusColorRole
        )
        created_on = index.data(CertificationsListModel.CreatedOnRole)
        updated_on = index.data(CertificationsListModel.UpdatedOnRole)
        expire_on = index.data(CertificationsListModel.ExpireOnRole)
        expire_on_color = index.data(CertificationsListModel.ExpireOnColorRole)

        """ Replace item row by the widget """
        widget = AccountCertificationRowWidget(
            account_address_label,
            account_name_label,
            identity_name_label,
            identity_status,
            identity_status_color,
            created_on,
            updated_on,
            expire_on,
            expire_on_color,
        )  # Crée le widget

        # set widget background
        # widget.setAutoFillBackground(True)

        # Modify widget palette depending on behavior (hover, selection)
        palette = widget.palette()

        # 🔹 Check if the line number is even (alternate color effect)
        if index.row() % 2 == 1:  # Even lines use AlternateBase color
            base_color = option.palette.color(QPalette.AlternateBase)
        else:
            base_color = option.palette.color(QPalette.Base)

        if option.state & QStyle.State_Selected:
            palette.setColor(
                QPalette.Window, option.palette.highlight().color()
            )  # selection color
        elif option.state & QStyle.State_MouseOver:
            palette.setColor(
                QPalette.Window, option.palette.color(QPalette.Highlight).lighter(200)
            )  # Hover
        else:
            palette.setColor(
                QPalette.Window, base_color
            )  # normal or alternate background

        widget.setPalette(palette)  # Apply palette on widget
        widget.update()  # 🔥 Force display of new colors

        widget.resize(option.rect.size())  # Set size to the row size
        painter.save()
        painter.translate(option.rect.topLeft())  # Place the widget correctly
        widget.render(painter)
        painter.restore()


class CertificationsListModel(QAbstractListModel):
    """
    CertificationsListModel class that drives the population of list display
    """

    (
        AccountAddressLabelRole,
        AccountNameRole,
        IdentityNameRole,
        IdentityStatusRole,
        IdentityStatusColorRole,
        CreatedOnRole,
        UpdatedOnRole,
        ExpireOnRole,
        ExpireOnColorRole,
    ) = range(Qt.UserRole, Qt.UserRole + 9)

    def __init__(
        self,
        application: Application,
        locale: QLocale,
        filter_column: str,
    ):
        """
        Init CertificationsReceivedListModel with identity index to get identity received certification list

        :param application: Application instance
        :param locale: QLocale instance
        :param filter_column: Column of Issuer or Receiver as TransfersRepositoryInterface.COLUMN_RECEIVER_IDENTITY_INDEX
        """
        super().__init__()

        self.application = application
        self._ = self.application.translator.gettext

        # get parent widget locale to localize balance display
        self.locale = locale
        self.filter_column = filter_column
        self.identity_index: Optional[int] = None
        self.certifications: List[Certification] = []

    def init_data(
        self, identity_index: Optional[int] = None, offset: int = 0, limit: int = 1000
    ):
        """
        Fill data from repository

        :param identity_index: Identiy index or None
        :param offset: Offset in DB list to paginate
        :param limit: Limit page size
        :return:
        """
        self.beginResetModel()
        self.identity_index = identity_index
        if self.identity_index is None:
            self.certifications = []
        else:
            self.certifications = self.application.certifications.repository.list(
                self.identity_index,
                filters={self.filter_column: self.identity_index},
                offset=offset,
                limit=limit,
                sort_column=CertificationsRepositoryInterface.COLUMN_EXPIRE_ON,
                sort_order=CertificationsRepositoryInterface.SORT_ORDER_ASCENDING,
            )
            logging.debug(
                "Certifications model init data, identity #%s, column=%s, size=%s",
                self.identity_index,
                self.filter_column,
                len(self.certifications),
            )
        self.endResetModel()

    def rowCount(self, _: QModelIndex = QModelIndex()) -> int:
        """
        Return row count

        :param _: QModelIndex instance
        :return:
        """
        return len(self.certifications)

    def index(  # type: ignore
        self, row: int, column: int, parent: QModelIndex = QModelIndex()
    ) -> QModelIndex:
        """
        Return QModelIndex for row, column and parent

        :param row: Row index
        :param column: Column index
        :param parent: Parent QModelIndex instance
        :return:
        """
        if not QAbstractItemModel.hasIndex(self, row, column, parent):
            return QModelIndex()

        if row < len(self.certifications):
            return QAbstractItemModel.createIndex(
                self, row, column, self.certifications[row]
            )

        return QModelIndex()

    def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> QVariant:
        """
        Return data of cell for column index.column

        :param index: QModelIndex instance
        :param role: Item data role
        :return:
        """
        row = index.row()
        certification = self.certifications[row]
        data = QVariant()

        # set certification issued or received depending on filter column
        address = (
            certification.issuer_address
            if self.filter_column
            == self.application.repository.certifications.COLUMN_RECEIVER_IDENTITY_INDEX
            else certification.receiver_address
        )
        identity_index = (
            certification.issuer_identity_index
            if self.filter_column
            == self.application.repository.certifications.COLUMN_RECEIVER_IDENTITY_INDEX
            else certification.receiver_identity_index
        )
        identity_name = (
            certification.issuer_identity_name
            if self.filter_column
            == self.application.repository.certifications.COLUMN_RECEIVER_IDENTITY_INDEX
            else certification.receiver_identity_name
        )
        identity_status = (
            certification.issuer_identity_status
            if self.filter_column
            == self.application.repository.certifications.COLUMN_RECEIVER_IDENTITY_INDEX
            else certification.receiver_identity_status
        )

        if role == self.AccountAddressLabelRole:
            # return address
            return QVariant(f"{address[:4]}…{address[-5:]}")

        if role == self.IdentityStatusRole:
            display_identity_status = {
                IdentityStatus.UNCONFIRMED.value: self._("Unconfirmed"),
                IdentityStatus.UNVALIDATED.value: self._("Unvalidated"),
                IdentityStatus.MEMBER.value: self._("Member"),
                IdentityStatus.NOT_MEMBER.value: self._("Not member"),
                IdentityStatus.REVOKED.value: self._("Revoked"),
            }
            return QVariant(display_identity_status[identity_status.value])

        if role == self.IdentityStatusColorRole:
            display_identity_status_color = {
                IdentityStatus.UNCONFIRMED.value: "orange",
                IdentityStatus.UNVALIDATED.value: "orange",
                IdentityStatus.MEMBER.value: "green",
                IdentityStatus.NOT_MEMBER.value: "red",
                IdentityStatus.REVOKED.value: "red",
            }
            return QVariant(display_identity_status_color[identity_status.value])

        if role == self.AccountNameRole:
            account = self.application.accounts.get_by_address(address)
            if account is not None and account.name is not None:
                return QVariant(account.name)
            else:
                return QVariant()

        if role == self.IdentityNameRole:
            if identity_index is not None:
                return QVariant(f"{identity_name}#{identity_index}")
            else:
                return QVariant()

        if role == self.CreatedOnRole:
            # datetime
            datetime_label = self.locale.toString(
                certification.created_on.astimezone(),
                QLocale.dateFormat(self.locale, QLocale.ShortFormat),
            )
            return QVariant(datetime_label)

        if role == self.UpdatedOnRole:
            if certification.updated_on > certification.created_on:
                # datetime
                datetime_label = self.locale.toString(
                    certification.updated_on.astimezone(),
                    QLocale.dateFormat(self.locale, QLocale.ShortFormat),
                )
                datetime_label = self._("(renewed on {datetime})").format(
                    datetime=datetime_label
                )
            else:
                datetime_label = ""
            return QVariant(datetime_label)

        if role == self.ExpireOnRole:
            # datetime
            datetime_label = self.locale.toString(
                certification.expire_on.astimezone(),
                QLocale.dateFormat(self.locale, QLocale.LongFormat),
            )
            return QVariant(datetime_label)

        if role == self.ExpireOnColorRole:
            color = NUMERIC_DISPLAY_COLOR_GREEN
            if certification.expire_on.astimezone() < datetime.now(
                timezone.utc
            ) + timedelta(weeks=24):
                color = NUMERIC_DISPLAY_COLOR_ORANGE
            if certification.expire_on.astimezone() < datetime.now(
                timezone.utc
            ) + timedelta(weeks=8):
                color = NUMERIC_DISPLAY_COLOR_RED
            return QVariant(color)

        return data
