import functools

import jingrow
import pyotp
from frappe import auth


def enabled(user_key: str = "user", raise_error: bool = False):
	"""
	Guard to check if two-factor authentication is enabled for a user.

	:param user_key: The key to retrieve the user from the request form if the session user is "Guest". Defaults to "user".
	:param raise_error: Whether to raise a PermissionError if 2FA is not enabled. Defaults to False.
	:raises frappe.PermissionError: If 2FA is not enabled and `raise_error` is True.
	"""

	def get_user() -> str:
		if frappe.session.user == "Guest":
			return (frappe.request.json or frappe.request.form).get(user_key, frappe.session.user)
		return frappe.session.user

	def wrapper(fn):
		@functools.wraps(fn)
		def inner(*args, **kwargs):
			if frappe.get_value("User 2FA", get_user(), "enabled"):
				return fn(*args, **kwargs)
			if raise_error:
				message = "Two-factor authentication is not enabled."
				frappe.throw(message, frappe.PermissionError)
			return None

		return inner

	return wrapper


def verify(user_key: str = "user", code_key: str = "totp_code", raise_error: bool = False):
	"""
	Guard to verify two-factor authentication for a user.

	:param user_key: The key to retrieve the user from the request form if the session user is "Guest". Defaults to "user".
	:param code_key: The key to retrieve the TOTP code from the request form. Defaults to "totp_code".
	:param raise_error: Whether to raise a PermissionError if 2FA verification fails. Defaults to False.
	:raises frappe.PermissionError: If 2FA verification fails and `raise_error` is True.
	"""

	def get_user() -> str:
		if frappe.session.user == "Guest":
			return (frappe.request.json or frappe.request.form).get(user_key, frappe.session.user)
		return frappe.session.user

	def get_code() -> str:
		return (frappe.request.json or frappe.request.form).get(code_key, "")

	def verify_code(user: str, code: str):
		secret = auth.get_decrypted_password("User 2FA", user, "totp_secret")
		return secret and pyotp.TOTP(secret).verify(code)

	def wrapper(fn):
		@functools.wraps(fn)
		def inner(*args, **kwargs):
			user = get_user()
			code = get_code()

			if user and not frappe.get_value("User 2FA", user, "enabled"):
				return fn(*args, **kwargs)
			if user and code and verify_code(user, code):
				return fn(*args, **kwargs)
			if raise_error:
				message = "Two-factor authentication verification failed."
				frappe.throw(message, frappe.PermissionError)
			return None

		return inner

	return wrapper
