import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { FormProvider, useForm } from 'react-hook-form';
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { useNavigate, useSearchParams } from 'react-router-dom';

import { useMsal } from '@azure/msal-react';
import { useOktaAuth } from '@okta/okta-react';
import { useGoogleLogin } from '@react-oauth/google';
import { useDebounce } from '@material-hu/hooks/useDebounce';
import { useModal } from '@material-hu/hooks/useModal';
import Typography from '@material-hu/mui/Typography';

import * as HuAuth from '@material-hu/components/composed-components/auth';
import HuDialog from '@material-hu/components/design-system/Dialog';
import useHuSnackbar from '@material-hu/components/design-system/Snackbar';

import adminBanner from 'src/assets/login-banner.webp';
import useAuth from 'src/contexts/JWTContext';
import useHuGoTheme from 'src/hooks/useHuGoTheme';
import { loginKeys } from 'src/pages/authentication/queries';
import { loginRoutes } from 'src/pages/authentication/routes';
import {
  getAzureInstances,
  getGoogleInstances,
  getInstances,
  getOktaInstances,
  getOTP,
  resetPassword,
} from 'src/services/authService';
import {
  type LoginData,
  LoginErrors,
  type LoginInstance,
} from 'src/types/login';
import { type RequestError } from 'src/types/services';
import { formatTitle } from 'src/utils/helmetUtils';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { LogEvents, logEvent } from 'src/utils/logging';
import { MIN_PASSWORD_LENGTH, validateRequired } from 'src/utils/validation';

const Login = () => {
  const { enqueueSnackbar } = useHuSnackbar();
  const { t } = useLokaliseTranslation('authentication');
  const { instance: azureInstance } = useMsal();
  const {
    login,
    loginViaJanus,
    loginWithMFA,
    loginWithAzure,
    loginWithOkta,
    loginWithGoogle,
    loginSaml,
  } = useAuth();
  const [email, setEmail] = useState(''); // needed because react-hook-form doesnt submit disabled input's values
  const [azureToken, setAzureToken] = useState<string | null>(null);
  const [oktaToken, setOktaToken] = useState<string | null>(null);
  const [googleToken, setGoogleToken] = useState<string | null>(null);
  const [ssoLoading, setSsoLoading] = useState(false);
  const [loadInstances, setLoadInstances] = useState(false);
  const [instances, setInstances] = useState<LoginInstance[]>([]);
  const [selectedInstance, setSelectedInstance] =
    useState<LoginInstance | null>(null);

  const [query, setQuery] = useState('');
  const HugoThemeProvider = useHuGoTheme();
  const debouncedQuery = useDebounce(query);
  const [searchParams] = useSearchParams();
  const token = searchParams.get('bearerToken');

  const navigate = useNavigate();
  const queryClient = useQueryClient();

  const { oktaAuth } = useOktaAuth();

  const hasExistantInstances =
    instances.length === 0 && !!debouncedQuery.length;

  const hasInstances = instances.length > 0 || hasExistantInstances;

  const form = useForm({
    defaultValues: {
      email: '',
      password: '',
    },
  });

  const {
    handleSubmit,
    formState: { isSubmitting },
    setError,
    watch,
  } = form;

  useEffect(() => {
    (async () => {
      if (token) {
        await loginSaml?.(token);
      }
    })();
  }, [token]);

  const setWrongCredentials = (field: 'email' | 'password') =>
    setError(field, {
      type: 'manual',
      message: t(field === 'email' ? 'wrong_user' : 'wrong_password'),
    });

  const startOtp = async (
    instance: LoginInstance,
    username: string,
    init2faToken?: string,
  ) => {
    const instanceId = instance.id;

    queryClient.prefetchQuery(loginKeys.otp.detail(instanceId, username), () =>
      getOTP(instanceId, username),
    );

    const state = init2faToken
      ? { instance, employeeInternalId: username, init2faToken }
      : { instance, employeeInternalId: username };

    navigate(loginRoutes.otp(), {
      state,
    });
  };

  const handleSelectInstance = (instance: LoginInstance, username = email) => {
    const { samlURI, forceOTP } = instance;

    if (samlURI) {
      window.location.replace(samlURI);
    }

    if (forceOTP) {
      return startOtp(instance, username);
    }

    closeSelectInstanceDrawer();
    setSelectedInstance(instance);
  };

  const currentEmail = watch('email');
  const currentPassword = watch('password');

  const requestParams = {
    username: currentEmail,
    limit: 50,
    search: debouncedQuery || undefined,
  };

  const { isFetching: loadingCommunities } = useQuery(
    loginKeys.instances(requestParams),
    () => getInstances(requestParams),
    {
      enabled: loadInstances,
      select: response => response.data,
      onSuccess: response => {
        if (response) {
          setInstances(response);
          showSelectInstanceDrawer(true);
          if (response.length) {
            setEmail(currentEmail);

            if (response.length === 1 && !debouncedQuery) {
              handleSelectInstance(response[0], currentEmail);
            }
          }
        } else {
          setLoadInstances(false);
        }
      },
      onError: (err: any) => {
        const code = err?.response?.data?.code;
        if (code === LoginErrors.USER_NOT_FOUND) {
          setWrongCredentials('email');
        } else {
          setError('email', {
            type: 'manual',
            message: t('error_unexpected'),
          });
        }
        setLoadInstances(false);
      },
    },
  );

  const handleLogin = async (values: { email: string; password: string }) => {
    const employeeInternalId = values.email || email;
    const body = {
      employeeInternalId,
      instanceId: selectedInstance?.id,
      password: values.password,
    };
    if (selectedInstance?.otpAfterRegularLogin) {
      const { init2faToken } = await loginWithMFA?.(body as LoginData)!;
      startOtp(selectedInstance, employeeInternalId, init2faToken);
    } else if (selectedInstance?.useJanusForClassicLogin) {
      await loginViaJanus?.(body as LoginData);
    } else {
      await login?.(body as LoginData);
    }
  };

  const submit = handleSubmit(async values => {
    try {
      if (!hasInstances) {
        setLoadInstances(true);
      } else {
        await handleLogin(values);
      }
    } catch (err: unknown) {
      const error = err as RequestError;
      const code = error?.response?.data?.code;
      if (
        code === LoginErrors.ACCESS_DENIED ||
        code === LoginErrors.MISSING_ADMIN_PERMISSION
      ) {
        enqueueSnackbar({
          title: t('not_enough_permissions'),
          variant: 'error',
        });
      } else if (code === LoginErrors.INVALID_CREDENTIALS) {
        setWrongCredentials(hasInstances ? 'password' : 'email');
      } else {
        setError(hasInstances ? 'password' : 'email', {
          type: 'manual',
          message: t('error_unexpected'),
        });
      }
    }
  });

  const azureLogin = async () => {
    try {
      setSsoLoading(true);
      const body = {
        accessToken: azureToken,
        instanceId: selectedInstance?.id,
      };

      await loginWithAzure?.(body);
    } catch (err: unknown) {
      const error = err as RequestError;
      setAzureToken(null);
      setSelectedInstance(null);
      setInstances([]);
      closeSelectInstanceDrawer();
      const errorCode = error?.response?.data?.code;
      if (errorCode === LoginErrors.ACCESS_DENIED) {
        enqueueSnackbar({
          title: t('not_enough_permissions'),
          variant: 'error',
        });
      } else if (
        errorCode === LoginErrors.SSO_MORE_THAN_ONE_USER_MATCHING_EMAIL
      ) {
        enqueueSnackbar({
          title: t('error_sso_login_duplicated_email'),
          variant: 'error',
        });
      } else {
        enqueueSnackbar({
          title: t('error_azure_login'),
          variant: 'error',
        });
      }
    } finally {
      setSsoLoading(false);
    }
  };

  const handleGetAzureInstances = async (newAzureToken: string) => {
    const response = await getAzureInstances(newAzureToken);
    if (response.data?.length) {
      if (response.data.length === 1) {
        setSelectedInstance(response.data[0]);
      } else {
        setInstances(response.data);
        showSelectInstanceDrawer({ showSearch: false });
      }
    } else {
      setAzureToken(null);
      enqueueSnackbar({
        title: t('microsoft_no_community'),
        variant: 'error',
      });
    }
  };

  const handleAzureLogin = async () => {
    try {
      setSsoLoading(true);
      const { accessToken: newAzureToken } =
        await azureInstance.acquireTokenPopup({
          scopes: ['profile', 'openid', 'User.Read'],
          redirectUri: '/',
          prompt: 'login',
        });
      setAzureToken(newAzureToken);
      await handleGetAzureInstances(newAzureToken);
    } catch (err: unknown) {
      const error = err as RequestError;
      const errorCode = error?.response?.data?.code;
      setAzureToken(null);
      setSelectedInstance(null);
      if (errorCode === LoginErrors.MICROSOFT_GRAPH_API_ERROR) {
        enqueueSnackbar({
          title: t('error_azure_graph_api'),
          variant: 'error',
          cancelAction: {
            text: t('configure_on_ms'),
            onClick: () => {
              window.open(
                'https://account.microsoft.com/?ref=MeControl&refd=portal.azure.com',
                '_blank',
              );
            },
          },
        });
      } else if (
        errorCode === LoginErrors.SSO_MORE_THAN_ONE_USER_MATCHING_EMAIL
      ) {
        enqueueSnackbar({
          title: t('error_sso_login_duplicated_email'),
          variant: 'error',
        });
      } else {
        enqueueSnackbar({
          title: t('error_azure_login'),
          variant: 'error',
        });
      }
    } finally {
      setSsoLoading(false);
    }
  };

  const oktaLogin = async () => {
    try {
      setSsoLoading(true);
      const body = {
        accessToken: oktaToken,
        instanceId: selectedInstance?.id,
      };

      await loginWithOkta?.(body);
    } catch (err: unknown) {
      const error = err as RequestError;
      const errorCode = error?.response?.data?.code;
      setOktaToken(null);
      setSelectedInstance(null);
      setInstances([]);
      closeSelectInstanceDrawer();
      if (errorCode === LoginErrors.ACCESS_DENIED) {
        enqueueSnackbar({
          title: t('not_enough_permissions'),
          variant: 'error',
        });
      } else if (
        errorCode === LoginErrors.SSO_MORE_THAN_ONE_USER_MATCHING_EMAIL
      ) {
        enqueueSnackbar({
          title: t('error_sso_login_duplicated_email'),
          variant: 'error',
        });
      } else {
        enqueueSnackbar({
          title: t('error_okta_login'),
          variant: 'error',
        });
      }
    } finally {
      setSsoLoading(false);
    }
  };

  const handleGetOktaInstances = async (newOktaToken: string) => {
    const response = await getOktaInstances(newOktaToken);
    if (response.data?.length) {
      if (response.data.length === 1) {
        setSelectedInstance(response.data[0]);
      } else {
        setInstances(response.data);
        showSelectInstanceDrawer({ showSearch: false });
      }
    } else {
      setOktaToken(null);
      enqueueSnackbar({
        title: t('okta_no_community'),
        variant: 'error',
      });
    }
  };

  const handleOktaLogin = async () => {
    try {
      setSsoLoading(true);
      const {
        tokens: { accessToken },
      } = await oktaAuth.token.getWithPopup({
        prompt: 'login',
      });
      const newOktaToken = accessToken?.accessToken!;
      setOktaToken(newOktaToken);
      await handleGetOktaInstances(newOktaToken);
    } catch (err: unknown) {
      const error = err as RequestError;
      const errorCode = error?.response?.data?.code;
      setOktaToken(null);
      setSelectedInstance(null);
      enqueueSnackbar({
        title: t(
          errorCode === LoginErrors.SSO_MORE_THAN_ONE_USER_MATCHING_EMAIL
            ? 'error_sso_login_duplicated_email'
            : 'error_okta_login',
        ),
        variant: 'error',
      });
    } finally {
      setSsoLoading(false);
    }
  };

  const googleLogin = async () => {
    try {
      setSsoLoading(true);
      const body = {
        accessToken: googleToken,
        instanceId: selectedInstance?.id,
      };

      await loginWithGoogle?.(body);
    } catch (err: unknown) {
      const error = err as RequestError;
      setGoogleToken(null);
      setSelectedInstance(null);
      setInstances([]);
      closeSelectInstanceDrawer();
      const errorCode = error?.response?.data?.code;
      if (errorCode === LoginErrors.ACCESS_DENIED) {
        enqueueSnackbar({
          title: t('not_enough_permissions'),
          variant: 'error',
        });
      } else if (
        errorCode === LoginErrors.SSO_MORE_THAN_ONE_USER_MATCHING_EMAIL
      ) {
        enqueueSnackbar({
          title: t('error_sso_login_duplicated_email'),
          variant: 'error',
        });
      } else {
        enqueueSnackbar({
          title: t('error_google_login'),
          variant: 'error',
        });
      }
    } finally {
      setSsoLoading(false);
    }
  };

  const handleGetGoogleInstances = async (newGoogleToken: string) => {
    const response = await getGoogleInstances(newGoogleToken);
    if (response.data?.length) {
      if (response.data.length === 1) {
        setSelectedInstance(response.data[0]);
      } else {
        setInstances(response.data);
        showSelectInstanceDrawer({ showSearch: false });
      }
    } else {
      setGoogleToken(null);
      enqueueSnackbar({
        title: t('google_no_community'),
        variant: 'error',
      });
    }
  };

  const googleLoginHook = useGoogleLogin({
    onSuccess: async ({ access_token }) => {
      try {
        setGoogleToken(access_token);
        await handleGetGoogleInstances(access_token);
      } catch (err: unknown) {
        const error = err as RequestError;
        const errorCode = error?.response?.data?.code;
        setGoogleToken(null);
        setSelectedInstance(null);
        enqueueSnackbar({
          title: t(
            errorCode === LoginErrors.SSO_MORE_THAN_ONE_USER_MATCHING_EMAIL
              ? 'error_sso_login_duplicated_email'
              : 'error_google_login',
          ),
          variant: 'error',
        });
      } finally {
        setSsoLoading(false);
      }
    },
    onError: () => {
      setSsoLoading(false);
    },
    onNonOAuthError: () => {
      setSsoLoading(false);
    },
  });

  const handleGoogleLogin = () => {
    setSsoLoading(true);
    googleLoginHook();
  };

  useEffect(() => {
    if (azureToken) {
      azureLogin();
    } else if (oktaToken) {
      oktaLogin();
    } else if (googleToken) {
      googleLogin();
    }
  }, [selectedInstance]);

  // selectedInstance is non-null when this fires — the recover-password button
  // is only rendered after an instance is selected (see handleRecoverPasswordOnClick).
  const resetPasswordMutation = useMutation(
    () =>
      resetPassword({
        employeeInternalId: email,
        instanceId: selectedInstance!.id,
      }),
    {
      onSuccess: (r, userHasEmail: boolean) => {
        enqueueSnackbar({
          title: t(userHasEmail ? 'email_sent_to_you' : 'email_sent_to_admin'),
          description: userHasEmail ? undefined : t('email_sent_to_admin_desc'),
          variant: 'success',
        });
        logEvent(LogEvents.USER_RESET_PASSWORD, {
          employeeInternalId: email,
          instanceId: selectedInstance?.id,
        });
      },
      onError: (err: unknown) => {
        const error = err as RequestError;
        const titleKey = ['INVALID_DATA', 'MISSING_USER_EMAIL'].includes(
          error?.response?.data.code!,
        )
          ? 'RESET_PASSWORD_ERROR'
          : 'RESET_PASSWORD_GENERAL_ERROR';
        enqueueSnackbar({
          title: t(titleKey, {
            context: error?.response?.data.code,
          }),
          variant: 'error',
        });
      },
    },
  );

  const {
    modal: confirmRecoverModal,
    showModal: openConfirmRecoverModal,
    closeModal: closeConfirmRecoverModal,
  } = useModal(
    () => (
      <HuDialog
        title={t('forgot_password_question')}
        textBody={t(
          selectedInstance?.userHasEmail
            ? 'confirmation_email_to_you'
            : 'confirmation_email_to_admin',
        )}
        primaryButtonProps={{
          children: t(
            selectedInstance?.userHasEmail ? 'send_email' : 'general:accept',
          ),
          onClick: () => {
            resetPasswordMutation.mutate(
              selectedInstance?.userHasEmail ?? false,
              { onSettled: closeConfirmRecoverModal },
            );
          },
          loading: resetPasswordMutation.isLoading,
        }}
        secondaryButtonProps={{
          children: t('general:cancel'),
          onClick: closeConfirmRecoverModal,
          disabled: resetPasswordMutation.isLoading,
        }}
        onClose={closeConfirmRecoverModal}
      />
    ),
    { fullWidth: true },
  );

  const handleRecoverPasswordOnClick = () => {
    const { userHasEmail } = selectedInstance!;
    openConfirmRecoverModal({ userHasEmail });
  };

  const clearInstances = () => {
    setInstances([]);
    setQuery('');
    setLoadInstances(false);
  };

  const handleSelectAnotherInstance = () => {
    showSelectInstanceDrawer(true);
    setSelectedInstance(null);
  };

  const {
    drawer: selectInstanceDrawer,
    showDrawer: showSelectInstanceDrawer,
    closeDrawer: closeSelectInstanceDrawer,
  } = HuAuth.useSelectIntanceDrawer({
    onClose: clearInstances,
    loading: loadingCommunities,
    instances,
    onSelectInstance: (instance: { name: string; logo: string }) => {
      handleSelectInstance(instance as LoginInstance);
    },
    searchProps: {
      query,
      setQuery,
    },
  } as any);

  return (
    <>
      <Helmet>
        <title>{formatTitle(t('login'))}</title>
      </Helmet>
      <HugoThemeProvider>
        {confirmRecoverModal}
        <FormProvider {...form}>
          <HuAuth.LoginLayout
            showBackdrop={ssoLoading}
            banner={{
              src: adminBanner,
              styles: {
                width: '85%',
              },
            }}
          >
            <HuAuth.LoginForm
              title={
                selectedInstance ? (
                  <HuAuth.InstanceCard
                    name={selectedInstance?.name ?? ''}
                    logo={selectedInstance?.logo ?? ''}
                  />
                ) : (
                  <Typography
                    variant="globalXL"
                    fontWeight="fontWeightSemiBold"
                  >
                    {t('login_into_admin_panel')}
                  </Typography>
                )
              }
              hasInstanceSelected={!!selectedInstance}
              showAnotherInstanceButton={
                !!selectedInstance && (instances.length > 1 || !!query.length)
              }
              isSubmitting={isSubmitting || loadingCommunities}
              submitDisabled={
                !!selectedInstance &&
                currentPassword.length < MIN_PASSWORD_LENGTH
              }
              formConfig={{
                email: {
                  rules: validateRequired(),
                },
                password: {
                  rules: validateRequired(),
                },
              }}
              callbacks={{
                onSubmit: submit,
                onRecoverPassword: handleRecoverPasswordOnClick,
                onSelectAnother: handleSelectAnotherInstance,
              }}
              sso={{
                azureButton: (
                  <HuAuth.SSOButton
                    type="Microsoft"
                    onClick={handleAzureLogin}
                  />
                ),
                googleButton: (
                  <HuAuth.SSOButton
                    type="Google"
                    onClick={handleGoogleLogin}
                  />
                ),
                oktaButton: (
                  <HuAuth.SSOButton
                    type="Okta"
                    onClick={handleOktaLogin}
                  />
                ),
              }}
            />
          </HuAuth.LoginLayout>
        </FormProvider>
        {selectInstanceDrawer}
      </HugoThemeProvider>
    </>
  );
};

export default Login;
