import json
import threading
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import AllowAny
from django.db import transaction
from app.helper import decorators
from app.applicationlayer.management.user.serializers import (
    UserManagementRetreiveSerializer
)
from app.entities.models import User, AuthToken
from datetime import datetime
from random import randrange
from django.conf import settings
from app.helper.email_service import sender
from app.applicationlayer.utils import main_threading
from rest_framework.exceptions import ParseError
from datetime import timedelta


class Login(ObtainAuthToken):
    @decorators.error_safe
    def post(self, request, *args, **kwargs):
        try:
            serializer = self.serializer_class(data=request.data,
                                               context={'request': request})
            serializer.is_valid(raise_exception=True)
            user = serializer.validated_data['user']
            token, created = Token.objects.get_or_create(user=user)

            if not created:
                token.created = datetime.now()
                token.save()

            return Response({
                'token': token.key,
                # 'user_id': user.pk,
                # 'email': user.email
            })
        except Exception as e:
            return Response(
                {"message": "Unable to log in with provided credentials."}
            )


class Logout(APIView):
    @decorators.error_safe
    def post(self, request, *args, **kwargs):
        existingUser = self.request.user
        if existingUser:
            existingToken = Token.objects.filter(user=existingUser).first()
            if existingToken:
                existingToken.delete()
                return Response(data={"detail": "User was logged out"},
                                status=status.HTTP_200_OK)
            else:
                return Response(data={"detail": "User session not found"},
                                status=status.HTTP_404_NOT_FOUND)
        else:
            return Response(data={"detail": "User not found"},
                            status=status.HTTP_404_NOT_FOUND)


class RefreshToken(APIView):
    @decorators.error_safe
    def post(self, request, token=None, *args, **kwargs):
        existingToken = Token.objects.filter(key=token).first()
        if existingToken:
            if existingToken.user == request.user:
                existingToken.created = datetime.now()
                existingToken.save()
                return Response(data={"detail": "Token refreshed"},
                                status=status.HTTP_200_OK)
            else:
                return Response(data={"detail": "Token user not match"},
                                status=status.HTTP_401_UNAUTHORIZED)
        else:
            return Response(data={"detail": "Token not found"},
                            status=status.HTTP_404_NOT_FOUND)


class CurrentUser(APIView):
    # @decorators.error_safe
    def get(self, request, token=None, *args, **kwargs):
        serializer = UserManagementRetreiveSerializer
        
        context = {"request": request}
        
        serializer = serializer(request.user, context=context)
        serializer.data['key'] = 'value'
        serialized = serializer.data

        return Response(data=serialized,
                        status=status.HTTP_200_OK)


'''
***********************
** RESET CREDENTIALS **
***********************
'''


class ForgotPassword(APIView):
    permission_classes = (AllowAny,)

    # @decorators.error_safe
    @transaction.atomic
    def post(self, request, *args, **kwargs):
        email = request.data['email']
        username = request.data['username']
        try:
            user = request.user.email
        except Exception as e:
            user = str(settings.CATCH_EMAIL)
        
        existingUser = User.objects.filter(
            email=email,
            username=username).first()
        
        date_now = datetime.now()
        
        if existingUser:
            # Check if there's existing request
            exToken = AuthToken\
                      .objects\
                      .filter(
                          user=existingUser.code,
                          is_active=True,
                          timeout_at__lt=date_now)\
                      .first()
            
            if exToken:
                raise ParseError('There is an existing password reset for this user.')
            
            REF = 'AUTH'
            TOKEN = ''
            PASSCODE = 0

            # Generate Random token for TOKEN
            TOKEN = Token().generate_key()
            
            # Generate Random number for PASSCODE
            rands = []
            rands.append(randrange(10))
            rands.append(randrange(10))
            rands.append(randrange(10))
            rands.append(randrange(10))
            PASSCODE = f"{rands[0]}{rands[1]}{rands[2]}{rands[3]}"
            
            date_now = datetime.now()
            timeout_at = date_now + timedelta(seconds=3600)

            AuthToken(
                ref=REF,
                token=TOKEN,
                passcode=PASSCODE,
                is_active=True,
                user=existingUser,
                created=date_now,
                timeout_at=timeout_at
            ).save()
            
            url = f"{settings.FRONT_END_URL}/forgot-password/reset"\
                  f"?token={TOKEN}"

            args = [str(PASSCODE), str(url), user, str(existingUser.email),
                    str(existingUser.name)]
            
            main_threading(args, sender.forgot_password)

            return Response(data={"detail": "Forgot Password Sent"},
                            status=status.HTTP_200_OK)
        else:
            return Response(data={"error": "User not found"},
                            status=status.HTTP_404_NOT_FOUND)


class ValidateForgotPasswordResetToken(APIView):
    permission_classes = (AllowAny,)

    @decorators.error_safe
    @transaction.atomic
    def post(self, request, *args, **kwargs):
        token = request.data['token']
        
        date_now = datetime.now()
        
        existingToken = AuthToken.objects.filter(token=token).first()
        if existingToken:
            if not existingToken.is_active:
                raise Exception('Request is no longer active')
            elif existingToken.timeout_at < date_now:
                raise Exception('Token already expired')
            
            return Response(data={
                                "username": existingToken.user.username,
                                "email": existingToken.user.email
                            },
                            status=status.HTTP_200_OK)
        else:
            return Response(data={"error": "Token not found"},
                            status=status.HTTP_404_NOT_FOUND)


class ForgotPasswordReset(APIView):
    permission_classes = (AllowAny,)

    @decorators.error_safe
    @transaction.atomic
    def post(self, request, *args, **kwargs):

        body_unicode = request.body.decode('utf-8')
        body_data = json.loads(body_unicode)

        username = body_data['username']
        password = body_data['password']
        password_confirm = body_data['password_confirm']
        passcode = body_data['passcode']
        token = body_data['token']

        if not username:
            raise Exception('Username is required')

        if not passcode:
            raise Exception('Passcode is required')

        if password != password_confirm:
            raise Exception('Passwords must match')

        existingToken = AuthToken.objects.filter(token=token).first()

        if existingToken:

            if existingToken.user.username != username:
                raise Exception('Username does not match')

            if not existingToken.is_active:
                raise Exception('Request is no longer active')

            if existingToken.passcode != passcode:
                raise Exception('Invalid Passcode')

            # TODO: Reset password here
            exUser = User.objects.filter(id=existingToken.user.id).first()
            exUser.set_password(password_confirm)
            exUser.save()

            existingToken.is_active = False
            existingToken.save()

            # sender.password_changed(
            #     str(existingToken.user.username),
            #     str(datetime.now()),
            #     str(existingToken.user.email))

            # args = [str(PASSCODE), str(url), str(existingUser.email), user]
            # t1 = threading.Thread(target=sender.forgot_password, args=(args,))
            # t1.start()

            # args = [str(PASSCODE), str(url), user, str(existingUser.email)]
            # t2 = threading.Thread(target=sender.forgot_password, args=(args,))
            # t2.start()

            return Response(data={"detail": "Forgot Password Reset Success"},
                            status=status.HTTP_200_OK)
        else:
            return Response(data={"error": "Token not found"},
                            status=status.HTTP_404_NOT_FOUND)
