# 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 typing import List

from gql import gql

from tikka.domains.entities.account import SearchAccount
from tikka.domains.entities.identity import IdentityStatus
from tikka.domains.entities.smith import SmithStatus
from tikka.interfaces.adapters.network.connection import NetworkConnectionError
from tikka.interfaces.adapters.network.indexer.accounts import (
    IndexerAccountsException,
    IndexerAccountsInterface,
)


class IndexerAccounts(IndexerAccountsInterface):
    """
    IndexerAccounts class
    """

    identity_status_map = {
        "Unconfirmed": IdentityStatus.UNCONFIRMED,
        "Unvalidated": IdentityStatus.UNVALIDATED,
        "Validated": IdentityStatus.MEMBER,
        "Member": IdentityStatus.MEMBER,
        "NotMember": IdentityStatus.NOT_MEMBER,
        "Revoked": IdentityStatus.REVOKED,
        "Removed": IdentityStatus.REMOVED,
    }

    smith_status_map = {
        "Invited": SmithStatus.INVITED,
        "Pending": SmithStatus.PENDING,
        "Smith": SmithStatus.SMITH,
        "Excluded": SmithStatus.EXCLUDED,
    }

    def is_legacy_v1(self, address: str) -> bool:
        __doc__ = IndexerAccountsInterface.is_legacy_v1.__doc__
        if (
            not self.indexer.connection.is_connected()
            or self.indexer.connection.client is None
        ):
            raise IndexerAccountsException(NetworkConnectionError())

        query = gql(
            f"""
            query {{
                accounts (filter: {{
                            createdOn: {{equalTo: 0  }},
                            id: {{equalTo: "{address}"}}
                        }}) {{ nodes {{
                                        id
                                    }}
                            }}
            }}
            """
        )
        try:
            result = self.indexer.connection.client.execute(query)
        except Exception as exception:
            raise IndexerAccountsException(exception)

        #
        #    {
        #     "account": [
        #       {
        #         "id": "xxxxxxxx"
        #       }
        #     ]
        #    }
        #
        return len(result["accounts"]["nodes"]) > 0

    def search(self, pattern: str, limit: int) -> List[SearchAccount]:
        __doc__ = IndexerAccountsInterface.search.__doc__
        if (
            not self.indexer.connection.is_connected()
            or self.indexer.connection.client is None
        ):
            raise IndexerAccountsException(NetworkConnectionError())

        if pattern.isdigit():
            identity_index_pattern = (
                f"{{ identity: {{ index: {{ equalTo: {pattern} }} }} }}"
            )
        else:
            identity_index_pattern = ""

        query = gql(
            f"""
            query {{
                  accounts(
                    first: {limit}
                    orderBy: ID_ASC
                    filter: {{
                      or: [
                        {{ id: {{ includesInsensitive: "{pattern}" }} }}
                        {{ identity: {{ name: {{ includesInsensitive: "{pattern}" }} }} }}
                        {identity_index_pattern}
                      ]
                    }}
                  ) {{
                    nodes {{
                      id
                      balance
                      createdOn
                      linkedIdentity {{
                        smith {{
                            smithStatus
                        }}
                        index
                        id
                        name
                        status
                      }}
                      identity {{
                        smith {{
                            smithStatus
                        }}
                        index
                        id
                        name
                        status
                      }}
                      totalBalance
                    }}
                    totalCount
                  }}
                }}
            """
        )

        #
        #    {
        #   "data": {
        #     "accounts": {
        #       "nodes": [
        #         {
        #           "id": "xxxxx",
        #           "createdOn": 631,
        #           "balance": "1000",
        #           "linkedIdentity": {
        #             "index": 1,
        #             "name": "alice",
        #             "status": "Member"
        #           },
        #           "identity": {
        #               "smith" {
        #                   "smithStatus": "Smith"
        #               },
        #             "index": 1,
        #             "name": "alice"
        #             "status": "Member"
        #           },
        #           "totalBalance": "1000"
        #         }
        #       ],
        #       "totalCount": 1
        #     }
        #   }
        # }
        #

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

        accounts: List[SearchAccount] = []
        if len(result["accounts"]["nodes"]) == 0:
            return accounts
        for row in result["accounts"]["nodes"]:
            search_account = SearchAccount(
                address=row["id"],
                balance=int(row["totalBalance"])
                if row["totalBalance"] > row["balance"]
                else int(row["balance"]),
                legacy_v1=row["createdOn"] == 0,
            )
            if row["identity"] is not None and "index" in row["identity"]:
                search_account.identity_index = row["identity"]["index"]
                search_account.identity_name = (
                    row["identity"]["name"]
                    if row["identity"]["name"] != row["identity"]["id"]
                    else None
                )
                search_account.identity_status = self.identity_status_map[
                    row["identity"]["status"]
                ]
                if row["identity"]["smith"] is not None:
                    search_account.smith_status = self.smith_status_map[
                        row["identity"]["smith"]["smithStatus"]
                    ]
                search_account.linked_identity = False
            elif row["linkedIdentity"] is not None and "index" in row["linkedIdentity"]:
                search_account.identity_index = row["linkedIdentity"]["index"]
                search_account.identity_name = (
                    row["linkedIdentity"]["name"]
                    if row["linkedIdentity"]["name"] != row["linkedIdentity"]["id"]
                    else None
                )
                search_account.identity_status = self.identity_status_map[
                    row["linkedIdentity"]["status"]
                ]
                if row["linkedIdentity"]["smith"] is not None:
                    search_account.smith_status = self.smith_status_map[
                        row["linkedIdentity"]["smith"]["smithStatus"]
                    ]
                search_account.linked_identity = True
            accounts.append(search_account)

        return accounts
