from datetime import timedelta
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.db import transaction
from django.utils.timezone import now
from django.utils.translation import ugettext as _
from rest_framework import serializers
from rest_framework.authtoken.models import Token
from .helpers import account_kit_me, account_kit_get_access_token
from .helpers import facebook_get_access_token, facebook_me
from .models import AccountKitUser, AccountKitAccessToken
from .models import FacebookUser, FacebookAccessToken
from .helpers import google_me, google_get_access_token
from .models import GoogleUser, GoogleAccessToken


class AuthResultSerializer(serializers.Serializer):
    def to_representation(self, instance: User):
        assert isinstance(instance, User)
        token, created = Token.objects.get_or_create(user=instance)
        return {
            'token': token.key,
            'user': {'id': instance.id, 'username': instance.username, 'email': instance.email},
        }


class AccountKitAuthTokenSerializer(serializers.Serializer):
    code = serializers.CharField(label=_("Code"), required=False)
    access_token = serializers.CharField(label=_("Access Token"), required=False)

    def validate(self, attrs):
        code = attrs.get('code', '')
        access_token = attrs.get('access_token', '')
        expire_time = None

        if not access_token:
            if not code:
                raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (NO_CODE)', code='authorization')
            request_time = now()
            res = account_kit_get_access_token(code)
            if res.status_code != 200:
                raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (INVALID_OR_USED_CODE)', code='authorization')
            data = res.json()
            access_token = data['access_token']
            expire_time = request_time + timedelta(seconds=data['token_refresh_interval_sec'])

        res = account_kit_me(access_token)
        if res.status_code != 200:
            raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (ME)', code='authorization')
        me = res.json()
        ak_user_id = me['id']

        with transaction.atomic():
            fields = ['me']
            ak_user, created = AccountKitUser.objects.get_or_create(ext_user_id=ak_user_id)
            if created:
                user = User(username='ak-{}'.format(ak_user_id))
                user.save()
                ak_user.user = user
                fields.append('user')
            ak_user.me = me
            ak_user.save(update_fields=fields)
            ak_token, created = AccountKitAccessToken.objects.get_or_create(ext_user=ak_user, access_token=access_token, defaults={'expire_time': expire_time})
            if ak_token.expire_time != expire_time:
                ak_token.expire_time = expire_time
                ak_token.save(update_fields=['expire_time'])

        attrs['user'] = ak_user.user
        return attrs


class FacebookAuthTokenSerializer(serializers.Serializer):
    code = serializers.CharField(label=_("Code"), required=False)
    access_token = serializers.CharField(label=_("Access Token"), required=False)

    def validate(self, attrs):
        code = attrs.get('code', '')
        access_token = attrs.get('access_token', '')
        expire_time = None

        if not access_token:
            if not code:
                raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (NO_CODE)', code='authorization')
            request_time = now()
            res = facebook_get_access_token(code)
            if res.status_code != 200:
                raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (INVALID_OR_USED_CODE)', code='authorization')
            data = res.json()
            access_token = data['access_token']
            expire_time = request_time + timedelta(seconds=data["expires_in"])

        res = facebook_me(access_token)
        if res.status_code != 200:
            raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (ME)', code='authorization')
        me = res.json()
        fb_user_id = me['id']

        with transaction.atomic():
            fields = ['me']
            fb_user, created = FacebookUser.objects.get_or_create(ext_user_id=fb_user_id)
            if created:
                user = User(username='fb-{}'.format(fb_user_id))
                user.save()
                fb_user.user = user
                fields.append('user')
            fb_user.me = me
            fb_user.save(update_fields=fields)
            fb_token, created = FacebookAccessToken.objects.get_or_create(ext_user=fb_user, access_token=access_token, defaults={'expire_time': expire_time})
            if fb_token.expire_time != expire_time:
                fb_token.expire_time = expire_time
                fb_token.save(update_fields=['expire_time'])

        attrs['user'] = fb_user.user
        return attrs


class GoogleAuthTokenSerializer(serializers.Serializer):
    code = serializers.CharField(label=_("Code"), required=False)
    access_token = serializers.CharField(label=_("Access Token"), required=False)

    def validate(self, attrs):
        code = attrs.get('code', '')
        access_token = attrs.get('access_token', '')
        expire_time = None

        if not access_token:
            if not code:
                raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (NO_CODE)', code='authorization')
            request_time = now()
            res = google_get_access_token(code)
            if res.status_code != 200:
                raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (INVALID_OR_USED_CODE)', code='authorization')
            data = res.json()
            access_token = data['access_token']
            expire_time = request_time + timedelta(seconds=data['expires_in'])

        res = google_me(access_token)
        if res.status_code != 200:
            raise serializers.ValidationError(_('Unable to log in with provided credentials.') + ' (ME)', code='authorization')
        me = res.json()
        gg_user_id = me['id']

        with transaction.atomic():
            fields = ['me']
            gg_user, created = GoogleUser.objects.get_or_create(ext_user_id=gg_user_id)
            if created:
                user = User(username='gg-{}'.format(gg_user_id))
                user.save()
                gg_user.user = user
                fields.append('user')
            gg_user.me = me
            gg_user.save(update_fields=fields)
            gg_token, created = GoogleAccessToken.objects.get_or_create(ext_user=gg_user, access_token=access_token, defaults={'expire_time': expire_time})
            if gg_token.expire_time != expire_time:
                gg_token.expire_time = expire_time
                gg_token.save(update_fields=['expire_time'])

        attrs['user'] = gg_user.user
        return attrs
