# Copyright (c) 2023, JINGROW
# For license information, please see license.txt

from __future__ import annotations

import random

import jingrow
import jingrow.utils
from jingrow.rate_limiter import rate_limit

from jcloud.api.account import get_account_request_from_key
from jcloud.jcloud.pagetype.team.team import Team
from jcloud.saas.pagetype.product_trial.product_trial import send_verification_mail_for_login
from jcloud.utils.telemetry import capture


def _get_active_site(product: str, team: str | None) -> str | None:
	if team is None:
		return None
	product_trial_linked_sites = jingrow.get_all(
		"Product Trial Request",
		{"product_trial": product, "team": team, "status": ["not in", ["Pending", "Error", "Expired"]]},
		pluck="site",
	)
	if not product_trial_linked_sites:
		return None
	existing_sites = jingrow.get_all(
		"Site",
		{
			"name": ["in", product_trial_linked_sites],
			"status": ["!=", "Archived"],
		},
		pluck="name",
		limit=1,
	)
	if len(existing_sites) > 0:
		return existing_sites[0]
	return None


@jingrow.whitelist(allow_guest=True)
def send_verification_code_for_login(email: str, product: str):
	is_user_exists = jingrow.db.exists("Team", {"user": email}) and _get_active_site(
		product, jingrow.db.get_value("Team", {"user": email}, "name")
	)
	if not is_user_exists:
		jingrow.throw("You have no active sites for this product. Please try signing up.")
	# generate otp and store in redis
	otp = random.randint(100000, 999999)
	jingrow.cache.set_value(
		f"product_trial_login_verification_code:{email}",
		jingrow.utils.sha256_hash(str(otp)),
		expires_in_sec=300,
	)

	send_verification_mail_for_login(email, product, otp)


@jingrow.whitelist(allow_guest=True)
@rate_limit(limit=10, seconds=300)
def login_using_code(email: str, product: str, code: str):
	team_exists = jingrow.db.exists("Team", {"user": email})
	site = _get_active_site(product, jingrow.db.get_value("Team", {"user": email}, "name"))
	if not team_exists:
		jingrow.throw("You have no active sites for this product. Please try signing up.")

	# check if team has 2fa enabled and active
	team = jingrow.get_value("Team", {"user": email}, ["name", "enforce_2fa", "enabled"], as_dict=True)
	if not team.enabled:
		jingrow.throw("Your account is disabled. Please contact support.")
	if team.enforce_2fa:
		jingrow.throw("Your account has 2FA enabled. Please go to jingrow.com to login.")

	# validate code
	code_hash_from_cache = jingrow.cache.get_value(f"product_trial_login_verification_code:{email}")
	if not code_hash_from_cache:
		jingrow.throw("OTP has expired. Please try again.")
	if jingrow.utils.sha256_hash(str(code)) != code_hash_from_cache:
		jingrow.throw("Invalid OTP. Please try again.")

	# remove code from cache
	jingrow.cache.delete_value(f"product_trial_login_verification_code:{email}")

	# login as user
	jingrow.set_user(email)
	jingrow.local.login_manager.login_as(email)

	# send the product trial request name
	return jingrow.get_value(
		"Product Trial Request",
		{"product_trial": product, "team": team.name, "site": site},
		pluck="name",
	)


@jingrow.whitelist(allow_guest=True)
@rate_limit(limit=5, seconds=60)
def get_account_request_for_product_signup():
	return jingrow.db.get_value("Account Request", {"email": jingrow.session.user}, "name")


@jingrow.whitelist(allow_guest=True, methods=["POST"])
def setup_account(key: str, country: str | None = None):
	ar = get_account_request_from_key(key)
	if not ar:
		jingrow.throw("Invalid or Expired Key")
	if not ar.product_trial:
		jingrow.throw("Invalid Product Trial")

	if country:
		ar.country = country
		ar.save(ignore_permissions=True)

	if not ar.country:
		jingrow.throw("Please provide a valid country name")

	jingrow.set_user("Administrator")
	# check if team already exists
	if jingrow.db.exists("Team", {"user": ar.email}):
		# Update first name and last name
		team = jingrow.get_pg("Team", {"user": ar.email})
		team.first_name = ar.first_name
		team.last_name = ar.last_name
		team.save(ignore_permissions=True)
	# create team
	else:
		# check if user exists
		is_user_exists = jingrow.db.exists("User", ar.email)
		team = Team.create_new(
			account_request=ar,
			first_name=ar.first_name,
			last_name=ar.last_name,
			country=ar.country,
			is_us_eu=ar.is_us_eu,
			user_exists=is_user_exists,
		)
	# Telemetry: Created account
	capture("completed_signup", "fc_saas", ar.email)
	# login
	jingrow.set_user(ar.email)
	jingrow.local.login_manager.login_as(ar.email)
	if _get_active_site(ar.product_trial, team.name):
		return {
			"account_request": ar.name,
			"location": f"/dashboard/saas/{ar.product_trial}/login-to-site?account_request={ar.name}",
		}

	return {
		"account_request": ar.name,
		"location": f"/dashboard/saas/{ar.product_trial}/setup?account_request={ar.name}",
	}


def _get_existing_trial_request(product: str, team: str):
	return jingrow.get_value(
		"Product Trial Request",
		{"team": team, "status": ["not in", ["Error", "Expired", "Site Created"]], "product_trial": product},
		["name", "site"],
		as_dict=True,
	)


@jingrow.whitelist(methods=["POST"])
def get_request(product: str, account_request: str | None = None):
	team = jingrow.local.team()

	# validate if there is already a site
	if site := _get_active_site(product, team.name):
		site_request = jingrow.get_pg(
			"Product Trial Request", {"product_trial": product, "team": team, "site": site}
		)
	elif request := _get_existing_trial_request(product, team.name):
		site_request = jingrow.get_pg("Product Trial Request", request.name)
	else:
		# check if account request is valid
		is_valid_account_request = jingrow.get_value("Account Request", account_request, "email") == team.user
		# create a new one
		site_request = jingrow.new_pg(
			"Product Trial Request",
			product_trial=product,
			team=team.name,
			account_request=account_request if is_valid_account_request else None,
		).insert(ignore_permissions=True)

	return {
		"name": site_request.name,
		"site": site_request.site,
		"product_trial": site_request.product_trial,
		"status": site_request.status,
	}
