import { useCallback, useEffect, useMemo, useReducer } from 'react';
import { Management } from 'auth0-js';
import { useLocation, useNavigate } from 'react-router-dom';
import { deepmerge } from '@mui/utils';

import { toastHandler } from 'components';
import { initialContext, WidgetContext } from './WidgetContext';
import WidgetReducer from './WidgetReducer';
import QueryProvider from 'providers/QueryProvider';
import { Auth0WebAuth } from 'utils/Auth0Auth';
import { getConnections } from 'utils';

import type {
	Auth0Error,
	Captcha,
	CrossOriginLoginOptions,
	DoneCallback,
	PasswordlessStartOptions,
	PasswordlessVerifyOptions,
} from 'auth0-js';

const initializeState: WidgetProvider.ContextInitializer = (
	state: WidgetProvider.InitialContext
) => {
	return {
		...state,
	} as WidgetProvider.Context;
};

export const WidgetProvider = ({
	authOptions,
	themeContent,
	children,
	...props
}: WidgetProvider.Props) => {
	authOptions = getConnections(authOptions);

	const auth0 = new Auth0WebAuth(authOptions);
	const location = useLocation();
	const navigate = useNavigate();

	const [context, dispatch] = useReducer<
		React.Reducer<WidgetProvider.Context, WidgetProvider.Action>,
		WidgetProvider.InitialContext
	>(
		WidgetReducer,
		{
			...initialContext,
			auth0,
			authOptions: authOptions,
			loginHint: authOptions?.loginHint,
			themeContent: deepmerge(initialContext?.themeContent, themeContent),
			...props,
		} as WidgetProvider.Context,
		initializeState
	);

	useEffect(() => {
		let isMounted = true;

		if (
			isMounted &&
			context?.flow === 'email' &&
			!location.pathname.includes('verify_redirect') &&
			!context?.isLoadingAuth &&
			auth0 &&
			context?.isAuthenticated === undefined
		) {
			checkSession();
		}

		return () => {
			isMounted = false;
		};
	}, [
		location?.pathname,
		auth0,
		context?.isLoadingAuth,
		context?.isAuthenticated,
		context?.flow,
	]);

	useEffect(() => {
		let isMounted = true;

		if (
			context?.isAuthenticated &&
			context?.managementToken &&
			!context?.auth0Manage &&
			isMounted
		) {
			const auth0Manage = new Management({
				domain: authOptions?.apiDomain,
				token: context?.managementToken,
			});

			dispatch({
				type: 'INITIATED_AUTH0_MANAGE',
				auth0Manage,
			});
		}

		return () => {
			isMounted = false;
		};
	}, [context?.isAuthenticated, authOptions, context?.auth0Manage]);

	const errorCallback = <T, E = Auth0Error>(error: E, res: T) => {
		if (res) {
			console.group('=== CALLBACK RESPONSE ===');
			console.log(JSON.stringify(res, null, 2));
			console.groupEnd();
		}

		if (error) {
			return toastHandler({ error });
		}
	};

	const goTo: WidgetProvider.Context['goTo'] = useCallback(
		(flow) =>
			dispatch({
				type: 'SWITCH_FLOW',
				flow,
			}),
		[]
	);

	const checkSession = useCallback(async () => {
		const {
			clientID,
			apiDomain: domain,
			redirectUri = window.location.origin,
			audience,
			scope,
		} = authOptions;

		let accessToken: string | undefined;

		if (!context?.isLoadingAuth) {
			dispatch({ type: 'INITIATED_CHECK_SESSION' });

			const payload: WidgetProvider.Action = {
				type: 'MANAGEMENT_SESSION_CHECKED',
				isAuthenticated: false,
				managementToken: undefined,
			};

			try {
				const result = await auth0.getTokens({
					clientID,
					domain,
					redirectUri,
					audience,
					scope,
					responseType: 'token id_token',
				});

				accessToken = result?.accessToken;
			} catch (error: unknown) {
				console.log(error);
				if (
					!['login_required', 'consent_required'].includes(
						(error as any)?.code
					)
				) {
					return toastHandler({ error });
				}
			}

			if (accessToken) {
				console.group('=== CHECK_SESSION RESPONSE ===');
				console.log(JSON.stringify(accessToken, null, 2));
				console.groupEnd();

				payload.isAuthenticated = !!accessToken;
				payload.managementToken = accessToken;
			}

			dispatch(payload);
		}
	}, [
		auth0.getTokens,
		context?.isLoadingAuth,
		window.location.origin,
		authOptions,
	]);

	const loginWithPassword: WidgetProvider.Context['loginWithPassword'] =
		useCallback(
			async ({
				email,
				username,
				password,
				connection: realm = authOptions?.connections
					?.password as string,
			}) => {
				const options: CrossOriginLoginOptions = {
					password,
					realm,
				};
				dispatch({ type: 'INITIATED_LOGIN_WITH_PASSWORD' });

				if (context?.captcha) {
					options['captcha'] = context.captcha.getValue();
				}

				if (username && email) {
					options['username'] = username;
				} else if (email) {
					options['email'] = email;
				}

				return auth0.login(options, errorCallback);
			},
			[
				auth0?.login,
				errorCallback,
				context?.captcha,
				authOptions?.connections?.password,
			]
		);

	const loginWithRedirect: WidgetProvider.Context['loginWithRedirect'] =
		useCallback(
			(options) => {
				const defaultOptions = {
					connection: context?.authOptions?.connections
						?.password as string,
					login_hint: context?.loginHint,
				};

				return auth0.authorize({ ...defaultOptions, ...options });
			},
			[
				auth0?.authorize,
				context?.authOptions?.connections?.password,
				context?.loginHint,
			]
		);

	const renderCaptcha: WidgetProvider.Context['renderCaptcha'] = useCallback(
		(container, isReRender = false) => {
			let captcha: Captcha;

			const cb = <T, E = Auth0Error>(error: E, res: T) => {
				if (res) {
					console.group('=== CALLBACK RESPONSE ===');
					console.log(JSON.stringify(res, null, 2));
					console.groupEnd();

					dispatch({ type: 'CAPTCHA_RENDERED' });
				}

				if (error) {
					if (captcha) {
						console.log('reloading captcha!');
						captcha.reload(cb as DoneCallback);
					}

					toastHandler({ error });

					return dispatch({
						type: 'CAPTCHA_ERROR',
						isInvalidCaptcha:
							(error as unknown as Auth0Error)?.name ===
							'invalid_captcha',
					});
				}
			};

			captcha = auth0.renderCaptcha(container, undefined, cb);

			if (isReRender) {
				return captcha.reload(cb as DoneCallback);
			}

			if (captcha) {
				dispatch({ type: 'CAPTCHA_RENDERED', captcha });
			}

			return captcha;
		},
		[]
	);

	// Method should only be called by useSendMagicLink hook!
	const sendMagicLink: WidgetProvider.Context['sendMagicLink'] = useCallback(
		async (options) => {
			const { email, resend = false, phoneNumber } = options || {};

			const emailConnection =
				context?.authOptions?.connections?.email ?? false;
			const smsConnection =
				context?.authOptions?.connections?.sms ?? false;

			try {
				const themeContent = {
					...context.themeContent[context.flow],
					hideLogo: true,
				};

				const payload: WidgetProvider.Action = {
					type: 'OTP_SENT',
				};

				const auth0Options: Partial<PasswordlessStartOptions> = {
					send: 'code',
				};

				dispatch({ type: 'INITIATED_OTP' });

				if (email) {
					if (!emailConnection) {
						return dispatch({
							type: 'ERROR',
							error: 'You must enable the `email` connection by setting `authOptions.connections.email` to `true` OR `email`.',
						});
					}

					auth0Options['connection'] = emailConnection as string;
					auth0Options['email'] = email;
					payload['loginHint'] = email;
					payload['flow'] = 'email-verify';
				} else if (phoneNumber) {
					if (!smsConnection) {
						return dispatch({
							type: 'ERROR',
							error: 'You must enable the `email` connection by setting `authOptions.connections.email` to `true` OR `email`.',
						});
					}

					auth0Options['connection'] = smsConnection as string;
					auth0Options['phoneNumber'] = phoneNumber;

					payload.flow = 'sms-verify';
					payload['loginHint'] = phoneNumber;

					await auth0.sendMagicLink({
						connection: smsConnection as string,
						send: 'code',
						phoneNumber,
					});
				}

				if (
					auth0Options?.connection &&
					(auth0Options?.email || auth0Options?.phoneNumber)
				) {
					await auth0.sendMagicLink(
						auth0Options as PasswordlessStartOptions
					);
				}

				if (resend) {
					themeContent.title = 'Link Resent!';
				}

				dispatch({
					...payload,
					themeContent: {
						...context?.themeContent,
						[context.flow]: { ...themeContent },
					},
				});
			} catch (error: unknown) {
				throw error;
			}
		},
		[
			auth0?.sendMagicLink,
			context?.authOptions?.connections,
			context.themeContent,
		]
	);

	// const getUser = (userId: string) => {
	// 	const cb = <T, E = Auth0Error>(error: E, user: T) => {
	// 		if (user) {
	// 			console.group('=== GET USER RESPONSE ===');
	// 			console.log(JSON.stringify(user, null, 2));
	// 			console.groupEnd();

	// 			return dispatch({ type: 'USER_FETCHED', payload: { user } });
	// 		}

	// 		if (error) {
	// 			console.group('=== UPDATE USER ERROR ===');
	// 			console.log(JSON.stringify(error, null, 2));
	// 			console.groupEnd();

	// 			return dispatch({ type: 'ERROR', error });
	// 		}
	// 	};
	// 	const auth0Manage = context?.auth0Manage;

	// 	if (auth0Manage) {
	// 		return auth0Manage.getUser(userId, cb);
	// 	}
	// };

	const updateUser: WidgetProvider.Context['updateUser'] = useCallback(
		({
			user_id: userId,
			given_name,
			family_name,
			postal_code,
			...data
		}) => {
			const cb = <
				T extends User.Profile = User.Profile,
				E extends Auth0Error = Auth0Error
			>(
				error: E,
				user: T
			) => {
				if (user) {
					console.group('=== UPDATE USER RESPONSE ===');
					console.log(JSON.stringify(user, null, 2));
					console.groupEnd();

					return dispatch({ type: 'USER_UPDATED', user });
				}

				if (error) {
					console.group('=== UPDATE USER ERROR ===');
					console.log(JSON.stringify(error, null, 2));
					console.groupEnd();

					return dispatch({ type: 'ERROR', error });
				}
			};

			const auth0Manage = context?.auth0Manage;

			if (auth0Manage) {
				if (postal_code) {
					auth0Manage.patchUserMetadata(userId, { postal_code }, cb);
				}

				if (given_name || family_name) {
					auth0Manage.getUser(
						userId,
						<Auth0UserProfile, Auth0Error>(
							userProfile: Auth0UserProfile,
							error: Auth0Error
						) => {
							if (userProfile) {
								auth0Manage.patchUserAttributes(
									userId,
									{
										name: '',
										nickname: '',
										picture: '',
										user_id: '',
										clientID: '',
										identities: [],
										created_at: '',
										updated_at: '',
										sub: '',
										...userProfile,
										given_name,
										family_name,
										...data,
									},
									cb
								);
							}

							if (error) {
								console.group('=== UPDATE USER ERROR ===');
								console.log(JSON.stringify(error, null, 2));
								console.groupEnd();

								return dispatch({ type: 'ERROR', error });
							}
						}
					);
				}
			}
		},
		[context?.auth0Manage]
	);

	// Method should only be called by useVerifyMagicLink hook!
	const verifyMagicLink: WidgetProvider.Context['verifyMagicLink'] =
		useCallback(
			async ({
				code: verificationCode,
				email,
				phone: phoneNumber,
				redirectUri = window.location.origin,
			}) => {
				const payload: WidgetProvider.Action = {
					type: 'OTP_VERIFIED',
					isLoadingAuth: false,
				};

				const auth0Options: Partial<PasswordlessVerifyOptions> = {
					verificationCode,
				};

				const emailConnection =
					context?.authOptions?.connections?.email ?? false;
				const smsConnection =
					context?.authOptions?.connections?.sms ?? false;

				try {
					dispatch({ type: 'INITIATED_OTP_VERIFY' });

					if (email && emailConnection) {
						auth0Options['connection'] = emailConnection as string;
						auth0Options['email'] = email;
					} else if (phoneNumber && smsConnection) {
						auth0Options['connection'] = smsConnection as string;
						auth0Options['phoneNumber'] = phoneNumber;
					}

					if (
						auth0Options?.connection &&
						(auth0Options?.email || auth0Options?.phoneNumber)
					) {
						payload['magicRes'] = await auth0.verifyMagicLink(
							auth0Options as PasswordlessVerifyOptions
						);
					}

					console.group('=== verifyMagicLink ===');
					console.log(JSON.stringify(payload?.magicRes, null, 2));
					console.groupEnd();

					dispatch(payload);

					if (redirectUri) {
						return navigate(redirectUri);
					}
				} catch (error: unknown) {
					throw error;
				}
			},
			[
				auth0?.verifyMagicLink,
				navigate,
				context?.authOptions,
				window?.location?.origin,
			]
		);

	const socialLogin: WidgetProvider.Context['socialLogin'] = useCallback(
		(connection) => {
			dispatch({ type: 'INITIATED_SOCIAL_AUTH' });

			return auth0.authorize({
				connection,
			});
		},
		[auth0?.authorize]
	);

	const contextValue: WidgetProvider.Context = useMemo(
		() => ({
			...context,
			checkSession,
			goTo,
			loginWithPassword,
			loginWithRedirect,
			renderCaptcha,
			sendMagicLink,
			socialLogin,
			updateUser,
			verifyMagicLink,
		}),
		[
			context,
			checkSession,
			goTo,
			loginWithPassword,
			loginWithRedirect,
			renderCaptcha,
			sendMagicLink,
			socialLogin,
			updateUser,
			verifyMagicLink,
		]
	);

	return (
		<WidgetContext.Provider value={contextValue}>
			<QueryProvider>{children}</QueryProvider>
		</WidgetContext.Provider>
	);
};

export default WidgetProvider;
