from django.utils.encoding import force_str
from django.utils.timezone import now
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response

from django_axor_auth.configurator import config
from django_axor_auth.users.api import get_request_user
from django_axor_auth.users.permissions import IsAuthenticated
from django_axor_auth.utils.error_handling.error_message import ErrorMessage
from .models import Totp


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def totp_init(request):
	# Get user
	user = get_request_user(request)
	# Create TOTP
	new_totp = Totp.objects.create_totp(user)
	if new_totp is None:
		return ErrorMessage(
			title='TOTP is already enabled',
			detail='You are attempting to setup TOTP but it is already enabled.',
			status=400,
			code='TOTPAlreadyEnabledOnInit',
			instance=request.build_absolute_uri()
		).to_response()
	key, backup_codes, _ = new_totp
	return Response(data={
		'key': key,
		'backup_codes': backup_codes,
		'provision': f"otpauth://totp/{config.APP_NAME}:{user.email.replace('@', '%40')}?secret={key}&issuer={config.APP_NAME}"
	}, status=201)


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def totp_enable(request):
	""" Enable TOTP for the user after user initiates the TOTP.

	Request Parameters:
		code (str): 6 len OTP code only
	"""
	token = force_str(request.data.get('code'))
	# If backup code is being used, then return 400 right away
	if len(token) > 6:
		return ErrorMessage(
			title='Invalid TOTP Token',
			detail='Please use a 6-digit TOTP token to enable TOTP. Backup codes cannot be used.',
			status=400,
			code='InvalidTOTPTokenTooLong',
			instance=request.build_absolute_uri()
		).to_response()
	# Try to authenticate the user
	try:
		# Get user
		user = get_request_user(request)
		# If authenticated, totp object will be returned otherwise None
		totp = Totp.objects.authenticate(user, token)
		# Only enable if the status is initialized
		# Disabled totp's cannot be enabled again
		if totp:
			if totp.status == 'initialized':
				totp.status = 'enabled'
				totp.save()
			return Response(status=200)
		return ErrorMessage(
			title='Invalid TOTP Token',
			detail='The provided TOTP token is invalid. Please try again.',
			status=400,
			code='InvalidTOTPToken',
			instance=request.build_absolute_uri()
		).to_response()
	except Totp.DoesNotExist:
		return ErrorMessage(
			title='TOTP not set',
			detail='You will need to start the TOTP setup process first.',
			status=400,
			code='TOTPNotSetup',
			instance=request.build_absolute_uri()
		).to_response()


@api_view(['PUT'])
@permission_classes([IsAuthenticated])
def totp_disable(request):
	""" Disable TOTP for the user.

	Request Parameters:
		code (str): 6 len OTP code or 8 len backup code
	"""
	# Get the token from request
	token = force_str(request.data.get('code'))
	# Try to authenticate the user
	totp = Totp.objects.authenticate(get_request_user(request), token)
	if totp:
		totp.status = 'disabled'
		totp.updated_at = now()
		totp.save()
		return Response(status=200)
	return ErrorMessage(
		title='Invalid TOTP Token',
		detail='The provided TOTP token or backup code is invalid. Please try again.',
		status=400,
		code='InvalidTOTPToken',
		instance=request.build_absolute_uri()
	).to_response()


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def totp_new_backup_codes(request):
	"""
	Generate new backup codes for the user. Invalidates the old backup codes.
	"""
	# Get the token from request
	token = force_str(request.data.get('code'))
	# Try to authenticate the user
	totp = Totp.objects.authenticate(get_request_user(request), token)
	if totp:
		# Create new backup codes
		backup_codes = Totp.objects.create_new_backup_codes(get_request_user(request))
		if backup_codes is None:
			return ErrorMessage(
				title='TOTP not set',
				detail='You will need to start the TOTP setup process first.',
				status=400,
				code='TOTPNotSetup',
				instance=request.build_absolute_uri()
			).to_response()
		return Response(data={'backup_codes': backup_codes}, status=200)
	return ErrorMessage(
		title='Invalid TOTP Token',
		detail='The provided TOTP token or backup code is invalid. Please try again.',
		status=400,
		code='InvalidTOTPToken',
		instance=request.build_absolute_uri()
	).to_response()
