import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";

import classes from "./classes.module.scss";
import LandingImage from "./landing-connect.jpeg";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import StepEmail from "./StepEmail";
import StepTotp from "./StepTotp";
import Auth from "@Front/Api/Auth/Customer/Auth";
import { ValidationError } from "class-validator";
import StepPassword from "./StepPassword";
import StepNewPassword from "./StepNewPassword";
import CustomerStore from "@Front/Stores/CustomerStore";
import Module from "@Front/Config/Module";
import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
import PasswordForgotten from "./PasswordForgotten";

export enum LoginStep {
	EMAIL,
	TOTP,
	PASSWORD,
	NEW_PASSWORD,
	PASSWORD_FORGOTTEN,
}
export default function Login() {
	const router = useRouter();
	const error = router.query["error"];

	const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);

	const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL);
	const [totpCodeUid, setTotpCodeUid] = useState<string>("");
	const [totpCode, setTotpCode] = useState<string>("");
	const [email, setEmail] = useState<string>("");
	const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
	const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);

	const openErrorModal = useCallback(() => {
		setIsErrorModalOpen(true);
	}, []);

	const closeErrorModal = useCallback(() => {
		setIsErrorModalOpen(false);
	}, []);

	useEffect(() => {
		if (error === "1") openErrorModal();
	}, [error, openErrorModal]);

	const onEmailFormSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
		try {
			if (!values["email"]) return;
			setEmail(values["email"]);
			const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
			setPartialPhoneNumber(res.partialPhoneNumber);
			setTotpCodeUid(res.totpCodeUid);
			setStep(LoginStep.TOTP);
			setValidationErrors([]);
		} catch (error: any) {
			setValidationErrors([
				{
					property: "email",
					constraints: {
						[error.http_status]: error.message,
					},
				},
			]);
			return;
		}
	}, []);

	const onSmsCodeSubmit = useCallback(
		async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
			try {
				if (!values["totpCode"]) return;
				const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email });

				// If the code is valid setting it in state
				if (res.validCode) setTotpCode(values["totpCode"]);

				setValidationErrors([]);
				// If it's first connection, show the form for first connection
				if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD);
				// If it's password forgotten, show the form for password forgotten
				else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN);
				// Else just login normally
				else setStep(LoginStep.PASSWORD);
			} catch (error: any) {
				setValidationErrors([
					{
						property: "totpCode",
						constraints: {
							[error.http_status]: error.message,
						},
					},
				]);
				return;
			}
		},
		[email, setStep],
	);

	const onNewPasswordSubmit = useCallback(
		async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
			try {
				if (!values["password"] || !values["confirm_password"]) return;
				if (values["password"] !== values["confirm_password"]) {
					setValidationErrors([
						{
							property: "confirm_password",
							constraints: {
								"400": "Les mots de passe ne correspondent pas.",
							},
						},
					]);
					return;
				}

				const passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/);
				if (!passwordRegex.test(values["password"])) {
					setValidationErrors([
						{
							property: "password",
							constraints: {
								"400": "Le mot de passe doit contenir au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.",
							},
						},
					]);
					return;
				}
				const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
				CustomerStore.instance.connect(token.accessToken, token.refreshToken);
				setValidationErrors([]);
				router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
				// If set password worked, setting the token and redirecting
			} catch (error: any) {
				setValidationErrors([
					{
						property: "password",
						constraints: {
							[error.http_status]: error.message,
						},
					},
				]);
				return;
			}
		},
		[totpCode, email, router],
	);

	const onPasswordSubmit = useCallback(
		async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
			try {
				if (!values["password"]) return;
				const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] });
				CustomerStore.instance.connect(token.accessToken, token.refreshToken);
				setValidationErrors([]);
				router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
			} catch (error: any) {
				setValidationErrors([
					{
						property: "password",
						constraints: {
							[error.http_status]: error.message,
						},
					},
				]);
				return;
			}
		},
		[email, router, totpCode],
	);

	const onPasswordForgotClicked = useCallback(async () => {
		try {
			const res = await Auth.getInstance().askNewPassword({ email });
			setPartialPhoneNumber(res.partialPhoneNumber);
			setValidationErrors([]);
			setStep(LoginStep.TOTP);
		} catch (error: any) {
			// If token already exists and is still valid redirect to the connect/register page
			if (error.http_status === 425) {
				setStep(LoginStep.TOTP);
				return;
			}
			return;
		}
	}, [email]);

	const onSendAnotherCode = useCallback(async () => {
		try {
			const res = await Auth.getInstance().sendAnotherCode({ email, totpCodeUid });

			setValidationErrors([]);
			setPartialPhoneNumber(res.partialPhoneNumber);
			setTotpCodeUid(res.totpCodeUid);
		} catch (error: any) {
			setValidationErrors([
				{
					property: "totpCode",
					constraints: {
						[error.http_status]: error.message,
					},
				},
			]);
			return;
		}
	}, [email, totpCodeUid]);

	return (
		<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
			<div className={classes["root"]}>
				{step === LoginStep.EMAIL && <StepEmail onSubmit={onEmailFormSubmit} validationErrors={validationErrors} />}
				{step === LoginStep.TOTP && (
					<StepTotp
						onSubmit={onSmsCodeSubmit}
						validationErrors={validationErrors}
						partialPhoneNumber={partialPhoneNumber}
						onSendAnotherCode={onSendAnotherCode}
					/>
				)}
				{step === LoginStep.PASSWORD && (
					<StepPassword
						onSubmit={onPasswordSubmit}
						validationErrors={validationErrors}
						onPasswordForgotClicked={onPasswordForgotClicked}
					/>
				)}
				{step === LoginStep.NEW_PASSWORD && <StepNewPassword onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />}
				{step === LoginStep.PASSWORD_FORGOTTEN && (
					<PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />
				)}
			</div>
			<Confirm
				isOpen={isErrorModalOpen}
				onClose={closeErrorModal}
				showCancelButton={false}
				onAccept={closeErrorModal}
				closeBtn
				header={"Erreur"}
				confirmText={"OK"}>
				<div className={classes["modal-content"]}>
					<Typography typo={ITypo.P_16} className={classes["text"]}>
						Une erreur est survenue lors de la connexion. Veuillez réessayer.
					</Typography>
				</div>
			</Confirm>
		</DefaultDoubleSidePage>
	);
}
