# 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 datetime import datetime
from typing import List, Optional

from dateutil import parser
from gql import gql

from tikka.domains.entities.transfer import Transfer
from tikka.interfaces.adapters.network.connection import NetworkConnectionError
from tikka.interfaces.adapters.network.indexer.transfers import (
    IndexerTransfersException,
    IndexerTransfersInterface,
)


class IndexerTransfers(IndexerTransfersInterface):
    """
    IndexerTransfers class
    """

    def list(
        self,
        address: str,
        limit: int,
        offset: int = 0,
        sort_column: str = IndexerTransfersInterface.COLUMN_TIMESTAMP,
        sort_order: str = IndexerTransfersInterface.SORT_ORDER_DESCENDING,
        from_datetime: Optional[datetime] = None,
        to_datetime: Optional[datetime] = None,
    ) -> List[Transfer]:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            IndexerTransfersInterface.list.__doc__
        )

        if (
            not self.indexer.connection.is_connected()
            or self.indexer.connection.client is None
        ):
            raise IndexerTransfersException(NetworkConnectionError())

        # Construire la condition de filtrage pour la date
        date_filter = []
        if from_datetime:
            date_filter.append(
                f'{{ timestamp: {{ greaterThanOrEqualTo: "{from_datetime.isoformat()}" }} }}'
            )
        if to_datetime:
            date_filter.append(
                f'{{ timestamp: {{ lessThanOrEqualTo: "{to_datetime.isoformat()}" }} }}'
            )

        date_filter_str = (
            ", and: [" + ", ".join(date_filter) + "]" if date_filter else ""
        )

        # Construire la requête GraphQL
        # Construire la condition de filtrage pour la date
        date_filter = []
        if from_datetime:
            date_filter.append(
                f'{{ timestamp: {{ greaterThanOrEqualTo: "{from_datetime.isoformat()}" }} }}'
            )
        if to_datetime:
            date_filter.append(
                f'{{ timestamp: {{ lessThanOrEqualTo: "{to_datetime.isoformat()}" }} }}'
            )

        date_filter_str = "and: [" + ", ".join(date_filter) + "]" if date_filter else ""

        # Construire la requête GraphQL
        query = f"""
            query {{
              transfers(
                first: {limit}
                offset: {offset}
                orderBy: {sort_column.upper()}_{sort_order.upper()}
                filter: {{
                    or: [
                        {{ fromId: {{ equalTo: "{address}" }} }},
                        {{ toId: {{ equalTo: "{address}" }} }}
                    ]
                    {date_filter_str}
                }}
              ) {{
                nodes {{
                    id
                    fromId
                    from {{
                      identity {{
                        index
                        name
                      }}
                    }}
                    toId
                    to {{
                      identity {{
                        index
                        name
                      }}
                    }}
                    amount
                    timestamp
                    comment {{
                      type
                      remark
                      remarkBytes
                    }}
                }}
              }}
            }}
        """

        try:
            result = self.indexer.connection.client.execute(gql(query))
        except Exception as exception:
            raise IndexerTransfersException(exception)

        transfers = []
        for transfer in result["transfers"]["nodes"]:
            issuer_identity_index = (
                transfer["from"]["identity"]["index"]
                if transfer["from"]["identity"]
                else None
            )
            issuer_identity_name = (
                transfer["from"]["identity"]["name"]
                if transfer["from"]["identity"]
                else None
            )
            receiver_identity_index = (
                transfer["to"]["identity"]["index"]
                if transfer["to"]["identity"]
                else None
            )
            receiver_identity_name = (
                transfer["to"]["identity"]["name"]
                if transfer["to"]["identity"]
                else None
            )

            comment = None
            comment_type = None
            if transfer["comment"]:
                comment = (
                    transfer["comment"]["remarkBytes"]
                    if transfer["comment"]["type"] == "RAW"
                    else transfer["comment"]["remark"]
                )
                comment_type = transfer["comment"]["type"]

            transfers.append(
                Transfer(
                    id=transfer["id"],
                    issuer_address=transfer["fromId"],
                    issuer_identity_index=issuer_identity_index,
                    issuer_identity_name=issuer_identity_name,
                    receiver_address=transfer["toId"],
                    receiver_identity_index=receiver_identity_index,
                    receiver_identity_name=receiver_identity_name,
                    amount=transfer["amount"],
                    timestamp=parser.parse(transfer["timestamp"]),
                    comment=comment,
                    comment_type=comment_type,
                )
            )

        return transfers

    def count(self, address) -> int:
        __doc__ = (  # pylint: disable=redefined-builtin, unused-variable
            IndexerTransfersInterface.count.__doc__
        )
        if (
            not self.indexer.connection.is_connected()
            or self.indexer.connection.client is None
        ):
            raise IndexerTransfersException(NetworkConnectionError())

        query = f"""
        query {{
          transfers(
            filter: {{
              or: [
                {{ fromId: {{ equalTo: "{address}" }} }}
                {{ toId: {{ equalTo: "{address}" }} }}
              ]
            }}
          ) {{
            totalCount
          }}
        }}
        """
        try:
            result = self.indexer.connection.client.execute(gql(query))
        except Exception as exception:
            raise IndexerTransfersException(exception)

        # {
        #     "data": {
        #         "transfers": {
        #             "totalCount": 916
        #         }
        #     }
        # }
        return result["transfers"]["totalCount"]
