# 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/>.
from functools import reduce
from typing import Any, Dict, List, Optional, Union

from dateutil import parser
from sql import Column, Delete, Flavor, Table
from sql.operators import And, Or

from tikka.domains.entities.identity import Certification, IdentityStatus
from tikka.interfaces.adapters.repository.certifications import (
    CertificationsRepositoryInterface,
)
from tikka.interfaces.adapters.repository.db_repository import DBRepositoryInterface

TABLE_NAME = "certifications"

# create sql table wrapper
sql_certifications_table = Table(TABLE_NAME)


class DBCertificationsRepository(
    CertificationsRepositoryInterface, DBRepositoryInterface
):
    """
    DBCertificationsRepository class
    """

    def list(
        self,
        identity_index: int,
        filters: Optional[Dict[str, Any]] = None,
        sort_column: str = CertificationsRepositoryInterface.COLUMN_CREATED_ON,
        sort_order: str = CertificationsRepositoryInterface.SORT_ORDER_ASCENDING,
        limit: int = 10,
        offset: int = 0,
    ) -> List[Certification]:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            CertificationsRepositoryInterface.list.__doc__
        )

        sql_columns = {
            CertificationsRepositoryInterface.COLUMN_ID: sql_certifications_table.id,
            CertificationsRepositoryInterface.COLUMN_ISSUER_IDENTITY_INDEX: sql_certifications_table.issuer_identity_index,
            CertificationsRepositoryInterface.COLUMN_ISSUER_IDENTITY_NAME: sql_certifications_table.issuer_identity_name,
            CertificationsRepositoryInterface.COLUMN_ISSUER_IDENTITY_STATUS: sql_certifications_table.issuer_identity_status,
            CertificationsRepositoryInterface.COLUMN_ISSUER_ADDRESS: sql_certifications_table.issuer_address,
            CertificationsRepositoryInterface.COLUMN_RECEIVER_IDENTITY_INDEX: sql_certifications_table.receiver_identity_index,
            CertificationsRepositoryInterface.COLUMN_RECEIVER_IDENTITY_NAME: sql_certifications_table.receiver_identity_name,
            CertificationsRepositoryInterface.COLUMN_RECEIVER_IDENTITY_STATUS: sql_certifications_table.receiver_identity_status,
            CertificationsRepositoryInterface.COLUMN_RECEIVER_ADDRESS: sql_certifications_table.receiver_address,
            CertificationsRepositoryInterface.COLUMN_CREATED_ON: sql_certifications_table.created_on,
            CertificationsRepositoryInterface.COLUMN_UPDATED_ON: sql_certifications_table.updated_on,
            CertificationsRepositoryInterface.COLUMN_EXPIRE_ON: sql_certifications_table.expire_on,
        }

        # if sort column...
        if sort_column is not None:
            # set sort column
            sql_sort_colum: Column = sql_columns[sort_column]
            # create select query wrapper with order by
            sql_select = sql_certifications_table.select(
                *sql_columns.values(),
                order_by=sql_sort_colum.asc
                if sort_order == CertificationsRepositoryInterface.SORT_ORDER_ASCENDING
                else sql_sort_colum.desc,
                limit=limit,
                offset=offset,
            )
        else:
            #  create select query wrapper without order by
            sql_select = sql_certifications_table.select(
                *sql_columns.values(), limit=limit, offset=offset
            )

        # create where conditions (issuer or receiver == identity index)
        conditions: List[Union[bool, Or]] = [
            Or(
                (
                    sql_columns[
                        CertificationsRepositoryInterface.COLUMN_ISSUER_IDENTITY_INDEX
                    ]
                    == identity_index,
                    sql_columns[
                        CertificationsRepositoryInterface.COLUMN_RECEIVER_IDENTITY_INDEX
                    ]
                    == identity_index,
                )
            )
        ]
        if filters is not None:
            for key, value in filters.items():
                conditions.append(sql_columns[key] == value)

        # conditions are added with and operator
        def and_(a, b):
            return And((a, b))

        if len(conditions) > 0:
            sql_select.where = reduce(and_, conditions)

        # config sql with ? as param style
        Flavor.set(Flavor(paramstyle="qmark"))

        sql, args = tuple(sql_select)
        result_set = self.client.select(sql, args)

        list_ = []
        for row in result_set:
            list_.append(get_certification_from_row(row))

        return list_

    def set_list(
        self, identity_index: int, certifications: List[Certification]
    ) -> None:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            CertificationsRepositoryInterface.set_list.__doc__
        )

        self.client.delete(TABLE_NAME, issuer_identity_index=identity_index)
        self.client.delete(TABLE_NAME, receiver_identity_index=identity_index)

        if len(certifications) > 0:
            # batch insert of certifications
            certification_rows = []
            for certification in certifications:
                certification_rows.append(get_fields_from_certification(certification))

            self.client.insert_many(TABLE_NAME, certification_rows)

    def delete_by_address(self, address: str, exclude: List[str]) -> None:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            CertificationsRepositoryInterface.delete_by_address.__doc__
        )
        sql_delete = Delete(
            sql_certifications_table,
            where=And(
                (
                    sql_certifications_table.issuer_address == address,
                    ~sql_certifications_table.receiver_address.in_(exclude),
                )
            ),
        )

        sql, args = tuple(sql_delete)
        self.client.execute(sql, args)

        sql_delete = Delete(
            sql_certifications_table,
            where=And(
                (
                    sql_certifications_table.receiver_address == address,
                    ~sql_certifications_table.issuer_address.in_(exclude),
                )
            ),
        )

        sql, args = tuple(sql_delete)
        self.client.execute(sql, args)

    def delete_all(self) -> None:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            CertificationsRepositoryInterface.delete_all.__doc__
        )
        self.client.clear(TABLE_NAME)


def get_fields_from_certification(certification: Certification) -> dict:
    """
    Return a dict of supported fields with normalized value

    :param certification: Certification instance
    :return:
    """
    fields = {}
    for (key, value) in certification.__dict__.items():
        if key.startswith("_"):
            continue
        if key == "issuer_identity_status":
            # convert IdentityStatus Enum to int
            value = value.value
        if key == "receiver_identity_status":
            # convert IdentityStatus Enum to int
            value = value.value
        fields[key] = value

    return fields


def get_certification_from_row(row: tuple) -> Certification:
    """
    Return a Certification instance from a result set row

    :param row: Result set row
    :return:
    """
    values: List[Any] = []
    count = 0
    for value in row:
        if count in (3, 7):
            # convert int to IdentityStatus Enum
            values.append(IdentityStatus(value))
        elif count in (9, 10, 11):
            # convert iso datetime string into datetime
            values.append(parser.parse(value))
        else:
            values.append(value)
        count += 1

    return Certification(*values)
