from typing import Any, Dict, List, Set, Tuple

from bemani.api.exceptions import APIException
from bemani.api.objects.base import BaseObject
from bemani.common import Profile, ValidatedDict, GameConstants, APIConstants
from bemani.data import UserID


class ProfileObject(BaseObject):

    def __format_ddr_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        return {
            'area': profile.get_int('area', -1) if exact else -1,
        }

    def __format_iidx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        qpro = profile.get_dict('qpro')

        return {
            'area': profile.get_int('pid', -1),
            'qpro': {
                'head': qpro.get_int('head', -1) if exact else -1,
                'hair': qpro.get_int('hair', -1) if exact else -1,
                'face': qpro.get_int('face', -1) if exact else -1,
                'body': qpro.get_int('body', -1) if exact else -1,
                'hand': qpro.get_int('hand', -1) if exact else -1,
            }
        }

    def __format_jubeat_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        return {}

    def __format_museca_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        return {}

    def __format_popn_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        return {
            'character': profile.get_int('chara', -1) if exact else -1,
        }

    def __format_reflec_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        return {
            'icon': profile.get_dict('config').get_int('icon_id', -1) if exact else -1,
        }

    def __format_sdvx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
        return {}

    def __format_profile(self, cardids: List[str], profile: Profile, settings: ValidatedDict, exact: bool) -> Dict[str, Any]:
        base = {
            'name': profile.get_str('name'),
            'cards': cardids,
            'registered': settings.get_int('first_play_timestamp', -1),
            'updated': settings.get_int('last_play_timestamp', -1),
            'plays': settings.get_int('total_plays', -1),
            'match': 'exact' if exact else 'partial',
        }

        if self.game == GameConstants.DDR:
            base.update(self.__format_ddr_profile(profile, exact))
        if self.game == GameConstants.IIDX:
            base.update(self.__format_iidx_profile(profile, exact))
        if self.game == GameConstants.JUBEAT:
            base.update(self.__format_jubeat_profile(profile, exact))
        if self.game == GameConstants.MUSECA:
            base.update(self.__format_museca_profile(profile, exact))
        if self.game == GameConstants.POPN_MUSIC:
            base.update(self.__format_popn_profile(profile, exact))
        if self.game == GameConstants.REFLEC_BEAT:
            base.update(self.__format_reflec_profile(profile, exact))
        if self.game == GameConstants.SDVX:
            base.update(self.__format_sdvx_profile(profile, exact))

        return base

    def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]:
        # Fetch the profiles
        profiles: List[Tuple[UserID, Profile]] = []
        if idtype == APIConstants.ID_TYPE_SERVER:
            profiles.extend(self.data.local.user.get_all_profiles(self.game, self.version))
        elif idtype == APIConstants.ID_TYPE_SONG:
            raise APIException(
                'Unsupported ID for lookup!',
                405,
            )
        elif idtype == APIConstants.ID_TYPE_INSTANCE:
            raise APIException(
                'Unsupported ID for lookup!',
                405,
            )
        elif idtype == APIConstants.ID_TYPE_CARD:
            users: Set[UserID] = set()
            for cardid in ids:
                userid = self.data.local.user.from_cardid(cardid)
                if userid is not None:
                    # Don't duplicate loads for users with multiple card IDs if multiples
                    # of those IDs are requested.
                    if userid in users:
                        continue
                    users.add(userid)

                    # We can possibly find another profile for this user. This is important
                    # in the case that we returned scores for a user that doesn't have a
                    # profile on a particular version. We allow that on this network, so in
                    # order to not break remote networks, try our best to return any profile.
                    profile = self.data.local.user.get_any_profile(self.game, self.version, userid)
                    if profile is not None:
                        profiles.append((userid, profile))
        else:
            raise APIException('Invalid ID type!')

        # Now, fetch the users, and filter out profiles belonging to orphaned users
        retval: List[Dict[str, Any]] = []
        id_to_cards: Dict[UserID, List[str]] = {}
        for (userid, profile) in profiles:
            if userid not in id_to_cards:
                cards = self.data.local.user.get_cards(userid)
                if len(cards) == 0:
                    # Can't add this user, skip the profile
                    continue

                id_to_cards[userid] = cards

            # Format the profile and add it
            settings = self.data.local.game.get_settings(self.game, userid)
            if settings is None:
                settings = ValidatedDict({})

            retval.append(self.__format_profile(id_to_cards[userid], profile, settings, profile.version == self.version))

        return retval
