# 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

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

from tikka.domains.entities.universal_dividend import UniversalDividend
from tikka.interfaces.adapters.repository.db_repository import DBRepositoryInterface
from tikka.interfaces.adapters.repository.transfers import TransfersRepositoryInterface
from tikka.interfaces.adapters.repository.universal_dividends import (
    UniversalDividendsRepositoryInterface,
)

TABLE_NAME = "universal_dividends"

# create sql table wrapper
sql_universal_dividends_table = Table(TABLE_NAME)


class DBUniversalDividendsRepository(
    UniversalDividendsRepositoryInterface, DBRepositoryInterface
):
    """
    DBUniversalDividendsRepository class
    """

    def list(
        self,
        address: str,
        filters: Optional[Dict[str, Any]] = None,
        sort_column: str = UniversalDividendsRepositoryInterface.COLUMN_TIMESTAMP,
        sort_order: str = UniversalDividendsRepositoryInterface.SORT_ORDER_DESCENDING,
        limit: int = 100,
        offset: int = 0,
    ) -> List[UniversalDividend]:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            UniversalDividendsRepositoryInterface.list.__doc__
        )

        sql_columns = {
            UniversalDividendsRepositoryInterface.COLUMN_ID: sql_universal_dividends_table.id,
            UniversalDividendsRepositoryInterface.COLUMN_ADDRESS: sql_universal_dividends_table.address,
            UniversalDividendsRepositoryInterface.COLUMN_IDENTITY_INDEX: sql_universal_dividends_table.identity_index,
            UniversalDividendsRepositoryInterface.COLUMN_IDENTITY_NAME: sql_universal_dividends_table.identity_name,
            UniversalDividendsRepositoryInterface.COLUMN_AMOUNT: sql_universal_dividends_table.amount,
            UniversalDividendsRepositoryInterface.COLUMN_TIMESTAMP: sql_universal_dividends_table.timestamp,
        }

        # 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_universal_dividends_table.select(
                *sql_columns.values(),
                order_by=sql_sort_colum.asc
                if sort_order == TransfersRepositoryInterface.SORT_ORDER_ASCENDING
                else sql_sort_colum.desc,
                limit=limit,
                offset=offset,
            )
        else:
            #  create select query wrapper without order by
            sql_select = sql_universal_dividends_table.select(
                *sql_columns.values(), limit=limit, offset=offset
            )

        # create where conditions
        conditions: List[Any] = [sql_universal_dividends_table.address == address]
        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_universal_dividend_from_row(row))

        return list_

    def add(self, universal_dividend: UniversalDividend) -> None:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            UniversalDividendsRepositoryInterface.add.__doc__
        )

        # insert only non hidden fields
        self.client.insert(
            TABLE_NAME,
            **get_fields_from_universal_dividend(universal_dividend),
        )

    def set_history(
        self, address: str, universal_dividends: List[UniversalDividend]
    ) -> None:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            UniversalDividendsRepositoryInterface.set_history.__doc__
        )
        # config sql with ? as param style
        Flavor.set(Flavor(paramstyle="qmark"))

        # delete orphan transfers not in cross table
        sql_delete = Delete(
            sql_universal_dividends_table,
            where=sql_universal_dividends_table.address == address,
        )

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

        if len(universal_dividends) > 0:
            # batch insert of transfers
            universal_dividend_rows = []
            for universal_dividend in universal_dividends:
                universal_dividend_rows.append(
                    get_fields_from_universal_dividend(universal_dividend)
                )
            self.client.insert_many(TABLE_NAME, universal_dividend_rows)

    def delete_by_address(self, address: str) -> None:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            UniversalDividendsRepositoryInterface.delete_by_address.__doc__
        )
        sql_delete = Delete(
            sql_universal_dividends_table,
            where=sql_universal_dividends_table.address == address,
        )

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

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

    def count(self, address: str) -> int:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            UniversalDividendsRepositoryInterface.count.__doc__
        )
        row = self.client.select_one(
            f"SELECT count(id) FROM {TABLE_NAME} WHERE address=?", (address,)
        )
        if row is None:
            return 0

        return row[0]


def get_fields_from_universal_dividend(universal_dividend: UniversalDividend) -> dict:
    """
    Return a dict of supported fields with normalized value

    :param universal_dividend: UniversalDividend instance
    :return:
    """
    fields = {}
    for (key, value) in universal_dividend.__dict__.items():
        if key.startswith("_"):
            continue
        fields[key] = value

    return fields


def get_universal_dividend_from_row(row: tuple) -> UniversalDividend:
    """
    Return a UniversalDividend instance from a result set row

    :param row: Result set row
    :return:
    """
    values: List[Any] = []
    count = 0
    for value in row:
        if count == 5:
            # python datetime does not handle timezone until 3.11
            values.append(parser.parse(value))
        else:
            values.append(value)
        count += 1

    return UniversalDividend(*values)
