import django.db.models
from typing import List, Tuple

import benchcoach.models
from benchcoach.models import BenchcoachModel, Availability, Player, Team, Positioning, Event, Venue
from teamsnap.teamsnap.api import TeamSnap
import teamsnap.models
from django.db.models import QuerySet

from benchcoach.utils.sync_engine import AbstractSyncEngine

class TeamsnapSyncEngine(AbstractSyncEngine):
    models = [
        Availability,
        Player,
        Team,
        # Positioning, # Not Implemented
        Event,
        Venue
    ]

    def __init__(self, managed_team_teamsnap_id, teamsnap_token):
        self.managed_teamsnap_team_id = managed_team_teamsnap_id
        self.client = TeamSnap(token=teamsnap_token)

    def _bulk_sync_from_teamsnap(self, qs:QuerySet):
        # -------------------------------------------------------------------------------------------
        # I hate having this translation. What I really want is just a property for "teamsnap_object"
        # which would simplify all this, but I couldn't figure out how to implement in the
        # teamsnap model foreign key and "related_name" that didn't cause conflicts. I don't
        # think I need to be too much smarter to figure this out, but alas I am not smart enough.
        # -------------------------------------------------------------------------------------------
        benchcoachmodel_to_teamsnapfield = {
            Availability:'teamsnap_availability',
            Player:'teamsnap_member',
            Team:'teamsnap_opponent',
            # Positioning:'teamsnap_lineupentry', # Not Implemented Yet, but will be 'teamsnap_lineupentry'
            Event:'teamsnap_event',
            Venue:'teamsnap_location'
        }

        teamsnapmodel_to_apiobject = {
            teamsnap.models.Availability: teamsnap.teamsnap.api.Availability,
            teamsnap.models.Event: teamsnap.teamsnap.api.Event,
            # teamsnap.models.LineupEntry:teamsnap.teamsnap.api.LineupEntry, # Not implemented Yet
            teamsnap.models.Location: teamsnap.teamsnap.api.Location,
            teamsnap.models.Member: teamsnap.teamsnap.api.Member,
            teamsnap.models.Opponent: teamsnap.teamsnap.api.Opponent,
            teamsnap.models.Team: teamsnap.teamsnap.api.Team,
            # teamsnap.models.User:teamsnap.teamsnap.api.User # Not implemented yet
        }

        apiobject_to_teamsnapmodel = {v:k for k,v in teamsnapmodel_to_apiobject.items()}

        if isinstance(qs.first(), benchcoach.models.Team):
            # This situation requires special attention because opponents and teams share a table in BenchCoach
            if getattr(qs.first(), 'teamsnap_team', None):
                teamsnap_attribute_name = 'teamsnap_team'
            elif getattr(qs.first(), 'teamsnap_opponent', None):
                teamsnap_attribute_name = 'teamsnap_opponent'
        else:
            teamsnap_attribute_name = benchcoachmodel_to_teamsnapfield.get(type(qs.first()))

        ids = [getattr(i, teamsnap_attribute_name).id for i in qs]
        ApiObject = teamsnapmodel_to_apiobject.get(type(getattr(qs.first(), teamsnap_attribute_name)))
        api_responses = ApiObject.search(client=self.client, id=",".join(ids))
        r = []
        for api_response in api_responses:
            teamsnap_instance = apiobject_to_teamsnapmodel.get(type(api_response)).objects.get(id=api_response.data['id'])
            response = self._update_from_teamsnapdata(teamsnap_instance, api_response)
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, teamsnap_instance.benchcoach_object)
            r.append(response)
        return r

    def _sync_from_teamsnap(self, benchcoach_instance:BenchcoachModel):

        # -------------------------------------------------------------------------------------------
        # I hate having this translation. What I really want is just a property for "teamsnap_object"
        # which would simplify all this, but I couldn't figure out how to implement in the
        # teamsnap model foreign key and "related_name" that didn't cause conflicts. I don't
        # think I need to be too much smarter to figure this out, but alas I am not smart enough.
        # -------------------------------------------------------------------------------------------
        benchcoachmodel_to_teamsnapfield = {
            Availability:'teamsnap_availability',
            Player:'teamsnap_member',
            Team:'teamsnap_opponent',
            # Positioning:'teamsnap_lineupentry', # Not Implemented Yet, but will be 'teamsnap_lineupentry'
            Event:'teamsnap_event',
            Venue:'teamsnap_location'
        }

        teamsnapmodel_to_apiobject = {
            teamsnap.models.Availability: teamsnap.teamsnap.api.Availability,
            teamsnap.models.Event: teamsnap.teamsnap.api.Event,
            # teamsnap.models.LineupEntry:teamsnap.teamsnap.api.LineupEntry, # Not implemented Yet
            teamsnap.models.Location: teamsnap.teamsnap.api.Location,
            teamsnap.models.Member: teamsnap.teamsnap.api.Member,
            teamsnap.models.Opponent: teamsnap.teamsnap.api.Opponent,
            teamsnap.models.Team: teamsnap.teamsnap.api.Team,
            # teamsnap.models.User:teamsnap.teamsnap.api.User # Not implemented yet
        }

        if isinstance(benchcoach_instance, benchcoach.models.Team):
            # This situation requires special attention because opponents and teams share a table in BenchCoach
            teamsnap_instance =  getattr(benchcoach_instance, 'teamsnap_team', None)
            if not teamsnap_instance: teamsnap_instance = getattr(benchcoach_instance, 'teamsnap_opponent')
        else:
            teamsnap_instance = getattr(benchcoach_instance, benchcoachmodel_to_teamsnapfield.get(type(benchcoach_instance)))

        ApiObject = teamsnapmodel_to_apiobject.get(type(teamsnap_instance))
        api_response = ApiObject.get(self.client, teamsnap_instance.id)

        r = self._update_from_teamsnapdata(teamsnap_instance, api_response)
        r = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
        return r

    def _update_from_teamsnapdata(self, teamsnap_instance:teamsnap.models.TeamsnapBaseModel, teamsnap_data: teamsnap.teamsnap.api.ApiObject) -> teamsnap.models.TeamsnapBaseModel:
        ''''''
        if isinstance(teamsnap_data, teamsnap.teamsnap.api.ApiObject):
            teamsnap_data = teamsnap_data.data
        else:
            raise TypeError
        if not teamsnap_data['type'] == teamsnap_instance.type:
            raise Exception()
        data_type = teamsnap_data['type']
        fields = ['id', 'created_at', 'updated_at']
        related_objects = {}
        if data_type in ['opponent', 'team', 'location']:
            fields += ['name']
        elif data_type == 'event':
            fields += [
                'label',
                'start_date',
                'formatted_title',
                'points_for_opponent',
                'points_for_team',
                'is_game',
                'game_type'
            ]
            if teamsnap_data.get('location_id'):
                related_objects['location'] = teamsnap.models.Location.objects.get(id=teamsnap_data['location_id'])
            if teamsnap_data.get('opponent_id'):
                related_objects['opponent'] = teamsnap.models.Opponent.objects.get(id=teamsnap_data['opponent_id'])
            pass

        elif data_type == 'member':
            fields += [
                'first_name',
                'last_name',
                'jersey_number',
                'is_non_player'
            ]

        elif data_type == 'availability':
            fields += ['status_code']
            related_objects['member'] = teamsnap.models.Member.objects.get(id=teamsnap_data['member_id'])
            related_objects['event'] = teamsnap.models.Event.objects.get(id=teamsnap_data['event_id'])

        else:
            raise ValueError

        if teamsnap_data.get('team_id'):
            related_objects['team'] = teamsnap.models.Team.objects.filter(id=teamsnap_data['team_id']).first()

        for field in fields:
            value = teamsnap_data.get(field)
            # if value is None:
            #     continue
            # else:
            setattr(teamsnap_instance,field,value)

        for related_object_name, related_object in related_objects.items():
            setattr(teamsnap_instance, related_object_name, related_object)

        teamsnap_instance.save()

        return teamsnap_instance

    def _update_teamsnapdb_to_benchcoachdb(self, teamsnap_instance, benchcoach_instance) -> List[Tuple[django.db.models.Model, bool]]:

        if isinstance(teamsnap_instance, teamsnap.models.Event):
            benchcoach_model = Event

            d = {
                'start': teamsnap_instance.start_date,
            }

            if teamsnap_instance.team:
                if teamsnap_instance.team.benchcoach_object:
                    if teamsnap_instance.game_type == "Home":
                        d['home_team'] = teamsnap_instance.team.benchcoach_object
                    elif teamsnap_instance.game_type == "Away":
                        d['away_team'] = teamsnap_instance.team.benchcoach_object
                elif not teamsnap_instance.team.benchcoach_object:
                    raise Team.DoesNotExist

            if teamsnap_instance.opponent:
                if teamsnap_instance.opponent.benchcoach_object:
                    if teamsnap_instance.game_type == 'Home':
                        d['away_team'] = teamsnap_instance.opponent.benchcoach_object
                    elif teamsnap_instance.game_type == 'Away':
                        d['home_team'] = teamsnap_instance.opponent.benchcoach_object
                elif not teamsnap_instance.opponent.benchcoach_object:
                    raise Team.DoesNotExist
                    pass

            if teamsnap_instance.location:
                if teamsnap_instance.location.benchcoach_object:
                    if teamsnap_instance.location:
                        d['venue'] = teamsnap_instance.location.benchcoach_object
                elif not teamsnap_instance.location.benchcoach_object:
                    raise Venue.DoesNotExist

        elif isinstance(teamsnap_instance, teamsnap.models.Opponent):
            benchcoach_model = Team
            d = {
                'name': teamsnap_instance.name,
            }

        elif isinstance(teamsnap_instance, teamsnap.models.Team):
            benchcoach_model = Team
            d = {
                'name': teamsnap_instance.name,
            }

        elif isinstance(teamsnap_instance, teamsnap.models.Location):
            benchcoach_model = Venue
            d = {
                'name': teamsnap_instance.name,
            }

        elif isinstance(teamsnap_instance, teamsnap.models.Member):
            benchcoach_model = Player
            d = {
                'first_name': teamsnap_instance.first_name,
                'last_name': teamsnap_instance.last_name,
                'jersey_number': teamsnap_instance.jersey_number,
            }

        elif isinstance(teamsnap_instance, teamsnap.models.Availability):
            benchcoach_model = Availability

            translation = {
                teamsnap_instance.YES: Availability.YES,
                teamsnap_instance.NO: Availability.NO,
                teamsnap_instance.MAYBE: Availability.MAYBE
            }

            d = {
                'available': translation.get(teamsnap_instance.status_code, Availability.UNKNOWN),
                'player': teamsnap_instance.member.benchcoach_object,
                'event': teamsnap_instance.event.benchcoach_object
            }

            r = []

            if teamsnap_instance.member.benchcoach_object:
                d['player'] = teamsnap_instance.member.benchcoach_object
            elif not teamsnap_instance.member.benchcoach_object:
                raise Player.DoesNotExist

            if teamsnap_instance.event.benchcoach_object:
                d['event'] = teamsnap_instance.event.benchcoach_object
            elif not teamsnap_instance.event.benchcoach_object:
                raise Event.DoesNotExist

        else:
            raise ValueError

        for field, value in d.items():
            setattr(benchcoach_instance, field, value)

        benchcoach_instance.save()
        teamsnap_instance.benchcoach_object = benchcoach_instance
        teamsnap_instance.save()
        return benchcoach_instance

    def _find_counterpart(self, instance):
        instance_type = type(instance)
        counterpart_instance = None
        if instance_type == Availability:
            counterpart_instance = instance.teamsnap_availability

        elif instance_type == Player:
            counterpart_instance = instance.teamsnap_member

        elif instance_type == Event:
            counterpart_instance = instance.teamsnap_event

        elif instance_type == Venue:
            counterpart_instance = instance.teamsnap_location

        elif instance_type == Team:
            if hasattr(instance, 'teamsnap_opponent'):
                counterpart_instance = instance.teamsnap_opponent
            elif hasattr(instance, 'teamsnap_team'):
                counterpart_instance = instance.teamsnap_team
            else:
                raise ValueError("instance doesn't seem to be an teamsnap opponent or a teamsnap team")

        elif instance_type == Positioning:
            counterpart_instance = instance.teamsnap_lineupentry

        else:
            raise Exception()

        return counterpart_instance

    def _sync_qs (self, qs, direction):
        if qs.model not in self.models:
            raise TypeError(f"Sync engine does not sync {qs.model} models")

        r=[]
        r = self._bulk_sync_from_teamsnap(qs)
        # for instance in qs:
            # r += self._sync_instance(instance, direction=direction)

        return r

    def _sync_instance(self, instance, direction, data=None):
        r=[]
        if direction == 'download':
            r.append(self._sync_from_teamsnap(instance))

        elif direction == 'upload':
            raise NotImplementedError('Uploading not supported by this sync engine yet.')
        else:
            raise TypeError(f"Direction {direction} not supported. 'upload' or 'download' must be specified")

        return r


    def sync(self, qs: django.db.models.QuerySet = None, instance: django.db.models.Model = None,
             direction='download') -> List[Tuple[django.db.models.Model, bool]]:
        if not qs and not instance:
            raise TypeError(f"sync requires either a QuerySet or model instance to be provided")
        if qs and instance:
            raise TypeError(f"sync requires either a QuerySet or model instance to be provided, but not both")
        elif qs:
            r = self._sync_qs(qs, direction)
        elif instance:
            r = self._sync_instance(instance, direction)

        return r

    def import_items(self, object_name=None, object_names=[]):
        # order is important

        ['team', 'opponent', 'location', 'member', 'event', 'availability']
        kwargs = {'client':self.client,'team_id': self.managed_teamsnap_team_id}
        r = {}

        # ---team---
        r['team'] = []
        for teamsnap_data in teamsnap.teamsnap.api.Team.search(client=self.client, id=self.managed_teamsnap_team_id):
            if teamsnap.models.Team.objects.filter(id=teamsnap_data.data['id']):
                teamsnap_instance = teamsnap.models.Team.objects.filter(id=teamsnap_data.data['id']).first()
                benchcoach_instance = teamsnap_instance.benchcoach_object
            else:
                teamsnap_instance = teamsnap.models.Team()
                benchcoach_instance = benchcoach.models.Team()
                teamsnap_instance.benchcoach_object=benchcoach_instance
                benchcoach_instance.save()
            response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
            teamsnap_instance.save()
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
            r['team'].append(response)

        # ---opponent---
        for teamsnap_data in teamsnap.teamsnap.api.Opponent.search(**kwargs):
            if teamsnap.models.Opponent.objects.filter(id=teamsnap_data.data['id']):
                teamsnap_instance = teamsnap.models.Opponent.objects.filter(id=teamsnap_data.data['id']).first()
                benchcoach_instance = teamsnap_instance.benchcoach_object
            else:
                teamsnap_instance = teamsnap.models.Opponent()
                benchcoach_instance = benchcoach.models.Team()
                teamsnap_instance.benchcoach_object = benchcoach_instance
                benchcoach_instance.save()
            response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
            r['team'].append(response)

        # ---location---
        r['location'] = []
        for teamsnap_data in teamsnap.teamsnap.api.Location.search(**kwargs):
            if teamsnap.models.Location.objects.filter(id=teamsnap_data.data['id']):
                teamsnap_instance = teamsnap.models.Location.objects.filter(id=teamsnap_data.data['id']).first()
                benchcoach_instance = teamsnap_instance.benchcoach_object
            else:
                teamsnap_instance = teamsnap.models.Location()
                benchcoach_instance = benchcoach.models.Venue()
                teamsnap_instance.benchcoach_object = benchcoach_instance
                benchcoach_instance.save()

            response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
            r['location'].append(response)

        # ---member---
        # Note: Non players not included in sync.
        r['member'] = []
        for teamsnap_data in teamsnap.teamsnap.api.Member.search(**kwargs,
                                                                 is_non_player = False
                                                                 ):
            if teamsnap_data.data['is_non_player'] == True:
                continue
            if teamsnap.models.Member.objects.filter(id=teamsnap_data.data['id']):
                teamsnap_instance = teamsnap.models.Member.objects.filter(id=teamsnap_data.data['id']).first()
                benchcoach_instance = teamsnap_instance.benchcoach_object
            else:
                teamsnap_instance = teamsnap.models.Member()
                benchcoach_instance = benchcoach.models.Player()
                teamsnap_instance.benchcoach_object = benchcoach_instance
                benchcoach_instance.save()

            response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
            r['member'].append(response)

        # ---event---
        r['event'] = []
        for teamsnap_data in teamsnap.teamsnap.api.Event.search(**kwargs):
            if teamsnap.models.Event.objects.filter(id=teamsnap_data.data['id']):
                teamsnap_instance = teamsnap.models.Event.objects.filter(id=teamsnap_data.data['id']).first()
                benchcoach_instance = teamsnap_instance.benchcoach_object
            else:
                teamsnap_instance = teamsnap.models.Event()
                benchcoach_instance = benchcoach.models.Event()
                teamsnap_instance.benchcoach_object = benchcoach_instance
                benchcoach_instance.save()

            response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
            r['event'].append(response)

        # ---availability---
        # Note: Non players not included in sync
        r['availability'] = []
        player_ids = [member.id for member in teamsnap.models.Member.objects.filter(is_non_player=False)]
        for teamsnap_data in teamsnap.teamsnap.api.Availability.search(**kwargs,
                                                                       member_id=",".join(player_ids)
                                                                       ):
            if teamsnap.models.Availability.objects.filter(id=teamsnap_data.data['id']):
                teamsnap_instance = teamsnap.models.Availability.objects.filter(id=teamsnap_data.data['id']).first()
                benchcoach_instance = teamsnap_instance.benchcoach_object
            else:
                teamsnap_instance = teamsnap.models.Availability()
                event_instance = benchcoach.models.Event.objects.get(teamsnap_event__id=teamsnap_data.data['event_id'])
                player_instance = benchcoach.models.Player.objects.get(teamsnap_member__id=teamsnap_data.data['member_id'])
                benchcoach_instance = benchcoach.models.Availability(event=event_instance, player=player_instance)
                benchcoach_instance.save()
                teamsnap_instance.benchcoach_object_id = benchcoach_instance.id
            response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
            response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
            r['availability'].append(response)

        return r