from typing import Optional, Type, TypeVar

from django.contrib.auth.models import update_last_login
from rest_framework import exceptions
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.models import TokenUser
from rest_framework_simplejwt.settings import api_settings
from rest_framework_simplejwt.tokens import Token, RefreshToken

from users.models import UserModel
from users.utils.interfaces import Response

AuthUser = TypeVar("AuthUser", UserModel, TokenUser)


class _ForgotPassword:
    token_class: Optional[Type[Token]] = RefreshToken

    def __init__(self, request, *args, **kwargs) -> None:
        self.request = request
        self.user = None

    def validate(self):

        data = self.request.data

        phone_number = data.get('phone_number')
        email_address = data.get('email_address')

        new_password = data.get('new_password')
        if new_password is None:
            raise ValidationError({'new_password': 'required.'})

        vcode = data.get('vcode')

        if phone_number:
            if not vcode:
                raise ValidationError({'vcode': 'required.'})
            user_obj = UserModel.objects.filter(phone_number=phone_number).first()
            authenticate = user_obj.authenticator('vcode', vcode) if user_obj is not None else False
            user_obj = user_obj if authenticate else None
            user_obj.verify_phone_number(vcode) if user_obj else None
            user_obj.change_password(new_password, vcode=vcode) if user_obj else None

        elif email_address:
            if not vcode:
                raise ValidationError({'vcode': 'required.'})
            user_obj = UserModel.objects.filter(email_address=email_address).first()
            authenticate = user_obj.authenticator('vcode', vcode) if user_obj is not None else False
            user_obj = user_obj if authenticate else None
            user_obj.verify_email_address(vcode) if user_obj else None
            user_obj.change_password(new_password, vcode=vcode) if user_obj else None

        else:
            raise ValidationError(
                {"detail": (
                    "To change your password, in addition to submitting a new password, "
                    "you must submit one of the following for authentication:\n"
                    "    1- Authentication with phone number and verification code(phone_number,vcode)\n"
                    "    2- Authentication with email address and verification code(email_address,vcode)\n"
                )})

        self.user = user_obj
        if not api_settings.USER_AUTHENTICATION_RULE(self.user):
            raise exceptions.AuthenticationFailed(
                "There is no user with these characteristics.",
                "no_active_account",
            )
        refresh = self.get_token(self.user)
        data = {
            'id': user_obj.id,
            "access": str(refresh),
            "refresh": str(refresh.access_token),
        }
        if api_settings.UPDATE_LAST_LOGIN:
            update_last_login(None, self.user)

        return data

    @classmethod
    def get_token(cls, user: AuthUser) -> Token:
        return cls.token_class.for_user(user)  # type: ignore


def as_view(request, *args, **kwargs):
    instance = _ForgotPassword(request)
    data = instance.validate()
    ret = Response(data=data, status_code=200)
    return ret
