import { IdentityProvider } from '@axinom/mosaic-id-utils';
import { ImgElement } from '@axinom/mosaic-ui';
import { Form, Formik } from 'formik';
import { PasswordPolicy, charsets } from 'password-sheriff';
import React, { useEffect } from 'react';
import axinom from '../../resources/images/axinom_auth_logo.svg';
import MosaicBG from '../../resources/images/bg.svg';
import google from '../../resources/images/google_logo.svg';
import imageNotFound from '../../resources/images/image_not_found.svg';
import azure from '../../resources/images/microsoft_logo.svg';
import mosaicLogo from '../../resources/images/mosaic_logo.svg';
import { IdentityProviderInfo } from '../../types';
import { ForgotPassword } from '../ForgotPassword/ForgotPassword';
import { useIdentityService } from '../IdentityServiceProvider/IdentityServiceProvider';
import { BrandingOptions } from '../common/BrandingOptions';
import classes from './Login.module.scss';

const DEFAULT_BG_IMAGE = MosaicBG;

const getIdpImageUrl = (idpImageUrl: string): string => {
  switch (idpImageUrl) {
    case 'DEFAULT_AZURE':
      return azure;
    case 'DEFAULT_GOOGLE':
      return google;
    case 'DEFAULT_AXINOM':
      return axinom;
    default:
      return idpImageUrl;
  }
};

export interface LoginProps {
  providers: IdentityProviderInfo[];
  brandingOptions?: BrandingOptions;
}

const passwordPolicy = new PasswordPolicy({
  length: { minLength: 10 },
  containsAtLeast: {
    atLeast: 3,
    expressions: [
      charsets.lowerCase,
      charsets.upperCase,
      charsets.numbers,
      charsets.specialCharacters,
    ],
  },
});

interface SignUpValidationErrors {
  firstName: boolean;
  lastName: boolean;
  email: boolean;
  password: boolean;
  confirmPassword: boolean;
}

/**
 * Renders the login component
 */
export const Login: React.FC<LoginProps> = ({ providers, brandingOptions }) => {
  const { getIdpAuthUrl, initiateSignUp, getWellKnownUrls } =
    useIdentityService();

  const originUrl = window.location.href;
  const background = brandingOptions?.background ?? DEFAULT_BG_IMAGE;

  const [axAuthManagementGraphQlEndpoint, setAxAuthManagementGraphQlEndpoint] =
    React.useState<string>();

  const providersToRender: IdentityProviderInfo[] = providers.filter(
    (idp) => idp.idpId !== IdentityProvider.AX_AUTH,
  );

  const enabledExternalProvidersCount = providers.filter(
    (idp) => idp.idpId !== 'AX_AUTH' && idp.enabled,
  ).length;

  const axAuthConfig = providers.filter(
    (idp) => idp.idpId === IdentityProvider.AX_AUTH,
  )[0];

  useEffect(() => {
    (async () => {
      const wellKnownUrls = await getWellKnownUrls();
      setAxAuthManagementGraphQlEndpoint(
        wellKnownUrls.axAuthManagementGraphQlEndpoint,
      );
    })();
  }, [getWellKnownUrls]);

  const backgroundStyles = {
    background: background?.includes('/') ? `url(${background})` : background,
    backgroundSize: `cover`,
    backgroundRepeat: `no-repeat`,
  };
  const activeTabClass = `${classes.tab} ${classes.active}`;

  const [activeTab, setActiveTab] = React.useState<'signIn' | 'signUp'>(
    'signIn',
  );
  const [isForgotPassword, setIsForgotPassword] =
    React.useState<boolean>(false);
  const [isSubmitted, setIsSubmitted] = React.useState<boolean>(false);

  const [signInUsername, setSignInUsername] = React.useState<string>('');
  const [signInPassword, setSignInPassword] = React.useState<string>('');

  const [signUpFirstName, setSignUpFirstName] = React.useState<string>('');
  const [signUpLastName, setSignUpLastName] = React.useState<string>('');
  const [signUpEmail, setSignUpEmail] = React.useState<string>('');
  const [signUpPassword, setSignUpPassword] = React.useState<string>('');
  const [signUpConfirmPassword, setSignUpConfirmPassword] =
    React.useState<string>('');
  const [errorMessage, setErrorMessage] = React.useState<string>();

  const [isEmailError, setIsEmailError] = React.useState<boolean>(false);
  const [isPasswordError, setIsPasswordError] = React.useState<boolean>(false);
  const [signUpValidationErrors, setSignUpValidationErrors] =
    React.useState<SignUpValidationErrors>({
      firstName: false,
      lastName: false,
      email: false,
      password: false,
      confirmPassword: false,
    });

  const [isSignUpSuccess, setIsSignUpSuccess] = React.useState<boolean>(false);

  const changeTab = (tab: 'signIn' | 'signUp'): void => {
    setActiveTab(tab);
    setErrorMessage('');
  };

  const isValidEmail = (email: string): boolean => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  };

  const directSignIn = async (): Promise<void> => {
    setErrorMessage(undefined);
    let hasClientValidationError = false;
    if (!isValidEmail(signInUsername)) {
      setIsEmailError(true);
      hasClientValidationError = true;
    } else {
      setIsEmailError(false);
    }
    if (signInPassword === '') {
      setIsPasswordError(true);
      hasClientValidationError = true;
    } else {
      setIsPasswordError(false);
    }
    if (hasClientValidationError) {
      return;
    }

    setIsSubmitted(true);
    const signInResponse = await fetch(
      getIdpAuthUrl(IdentityProvider.AX_AUTH, originUrl),
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email: signInUsername,
          password: signInPassword,
        }),
        credentials: 'include',
      },
    );
    setIsSubmitted(false);

    if (signInResponse.ok) {
      window.location.assign(originUrl);
    } else {
      const signInResponseJson = await signInResponse.json();
      setErrorMessage(signInResponseJson.message);
    }
  };

  const hasSignUpValidationErrors = (): boolean => {
    const validationErrors: SignUpValidationErrors = {
      firstName: false,
      lastName: false,
      email: false,
      password: false,
      confirmPassword: false,
    };
    let hasClientValidationError = false;
    if (signUpFirstName === '') {
      validationErrors.firstName = true;
      hasClientValidationError = true;
    } else {
      validationErrors.firstName = false;
    }
    if (signUpLastName === '') {
      validationErrors.lastName = true;
      hasClientValidationError = true;
    } else {
      validationErrors.lastName = false;
    }
    if (!isValidEmail(signUpEmail)) {
      validationErrors.email = true;
      hasClientValidationError = true;
    } else {
      validationErrors.email = false;
    }
    if (signUpPassword === '') {
      validationErrors.password = true;
      hasClientValidationError = true;
    } else {
      validationErrors.password = false;
    }
    if (
      signUpConfirmPassword === '' ||
      signUpPassword !== signUpConfirmPassword
    ) {
      validationErrors.confirmPassword = true;
      hasClientValidationError = true;
    } else {
      validationErrors.confirmPassword = false;
    }

    setSignUpValidationErrors(validationErrors);
    if (hasClientValidationError) {
      return true;
    }
    return false;
  };

  const initiateSignUpFlow = async (): Promise<void> => {
    setErrorMessage(undefined);

    if (hasSignUpValidationErrors()) {
      return;
    }
    if (!passwordPolicy.check(signUpPassword)) {
      setErrorMessage(
        'Password does not meet the policy requirements. It should contain at least 10 characters, and at least 3 of the following 4 types of characters: lowercase letters, uppercase letters, numbers and special characters.',
      );
      return;
    }

    if (axAuthManagementGraphQlEndpoint !== undefined) {
      setIsSubmitted(true);
      const initiateSignUpResponse = await initiateSignUp(
        {
          firstName: signUpFirstName,
          lastName: signUpLastName,
          email: signUpEmail,
          password: signUpPassword,
        },
        axAuthManagementGraphQlEndpoint,
      );
      setIsSubmitted(false);
      if (initiateSignUpResponse.isSuccess) {
        setIsSignUpSuccess(true);
      } else {
        setErrorMessage(
          initiateSignUpResponse.errorMessage ??
            'Unexpected Error occurred while attempting to sign up. Please try again later.',
        );
      }
    } else {
      setErrorMessage('AxAuthManagementGraphQlEndpoint is not available');
    }
  };

  return (
    <>
      {!isForgotPassword && (
        <>
          <div
            className={classes.container}
            style={backgroundStyles}
            data-test-id="login-page"
          >
            <div className={classes.whiteBox}>
              <div className={classes.logo}>
                <ImgElement
                  alt="Customer logo"
                  src={brandingOptions?.companyLogo ?? mosaicLogo}
                />
              </div>
              {axAuthConfig?.enabled && (
                <div className={classes.tabs}>
                  <div
                    className={
                      activeTab === 'signIn' ? activeTabClass : classes.tab
                    }
                    onClick={() => changeTab('signIn')}
                  >
                    Sign In
                  </div>
                  <div
                    className={
                      activeTab === 'signUp' ? activeTabClass : classes.tab
                    }
                    onClick={() => changeTab('signUp')}
                  >
                    Sign Up
                  </div>
                </div>
              )}
              <div className={classes.tabContent}>
                <div
                  id="signIn"
                  className={
                    activeTab === 'signIn' ? classes.active : classes.content
                  }
                >
                  {errorMessage && (
                    <div
                      className={classes.errorMessage}
                      data-test-id="login-error-message"
                    >
                      {errorMessage}
                    </div>
                  )}

                  {axAuthConfig?.enabled && (
                    <>
                      <Formik
                        initialValues={{}}
                        onSubmit={async () => {
                          await directSignIn();
                        }}
                      >
                        <Form>
                          <div
                            className={
                              isEmailError
                                ? classes.inputGroupError
                                : classes.inputGroup
                            }
                          >
                            <input
                              type="text"
                              id="login-username"
                              value={signInUsername}
                              onChange={(e) =>
                                setSignInUsername(e.target.value)
                              }
                              placeholder="email"
                              data-test-id="login-username"
                            />
                            {isEmailError && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="login-username-error"
                              >
                                Email must be a valid email.
                              </div>
                            )}
                          </div>
                          <div
                            className={
                              isPasswordError
                                ? classes.inputGroupError
                                : classes.inputGroup
                            }
                          >
                            <input
                              type="password"
                              value={signInPassword}
                              onChange={(e) =>
                                setSignInPassword(e.target.value)
                              }
                              autoComplete="current-password"
                              id="login-password"
                              placeholder="password"
                              data-test-id="login-password"
                            />
                            {isPasswordError && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="login-password-error"
                              >
                                Password cannot be empty.
                              </div>
                            )}
                          </div>
                          <button
                            type="submit"
                            className={
                              isSubmitted
                                ? classes.buttonDisabled
                                : classes.button
                            }
                            data-test-id="signin-button"
                          >
                            Sign In
                          </button>
                        </Form>
                      </Formik>

                      <div
                        className={classes.forgotPassword}
                        data-test-id="forgot-password"
                      >
                        <a onClick={() => setIsForgotPassword(true)}>
                          Forgot Password
                        </a>
                      </div>
                    </>
                  )}

                  {axAuthConfig?.enabled &&
                    enabledExternalProvidersCount > 0 && (
                      <div className={classes.separator}>
                        <span>or</span>
                      </div>
                    )}

                  {providersToRender.map(
                    (idp) =>
                      idp.enabled && (
                        <div
                          className={classes.buttonIdp}
                          key={idp.idpId}
                          onClick={() => {
                            window.location.assign(
                              getIdpAuthUrl(idp.idpId, originUrl),
                            );
                          }}
                          data-test-id={`login-with:${idp.idpId}`}
                        >
                          <ImgElement
                            alt={idp.displayName}
                            src={getIdpImageUrl(idp.idpImageUrl)}
                            onError={({ currentTarget }) => {
                              currentTarget.onerror = null;
                              currentTarget.src = imageNotFound;
                            }}
                          />

                          <div className={classes.text}>{idp.displayName}</div>
                        </div>
                      ),
                  )}
                </div>
                <div
                  id="signUp"
                  className={
                    activeTab === 'signUp' ? classes.active : classes.content
                  }
                >
                  {errorMessage && (
                    <div className={classes.errorMessage}>{errorMessage}</div>
                  )}
                  {!isSignUpSuccess && (
                    <>
                      <Formik
                        initialValues={{}}
                        onSubmit={async () => {
                          await initiateSignUpFlow();
                        }}
                      >
                        <Form>
                          <div className={classes.inputGroup}>
                            <input
                              type="text"
                              value={signUpFirstName}
                              onChange={(e) => {
                                setSignUpFirstName(e.target.value);
                              }}
                              onBlur={() => {
                                hasSignUpValidationErrors();
                              }}
                              id="register-firstname"
                              placeholder="first name"
                              data-test-id="signup-firstname"
                            />
                            {signUpValidationErrors.firstName && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="signup-firstname-error"
                              >
                                First name cannot be empty.
                              </div>
                            )}
                          </div>
                          <div className={classes.inputGroup}>
                            <input
                              type="text"
                              value={signUpLastName}
                              onChange={(e) =>
                                setSignUpLastName(e.target.value)
                              }
                              onBlur={() => {
                                hasSignUpValidationErrors();
                              }}
                              id="register-lastname"
                              placeholder="last name"
                              data-test-id="signup-lastname"
                            />
                            {signUpValidationErrors.lastName && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="signup-lastname-error"
                              >
                                Last name cannot be empty.
                              </div>
                            )}
                          </div>
                          <div className={classes.inputGroup}>
                            <input
                              type="email"
                              value={signUpEmail}
                              onChange={(e) => setSignUpEmail(e.target.value)}
                              onBlur={() => {
                                hasSignUpValidationErrors();
                              }}
                              id="register-email"
                              placeholder="email"
                              data-test-id="signup-email"
                            />
                            {signUpValidationErrors.email && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="signup-email-error"
                              >
                                Email must be a valid email.
                              </div>
                            )}
                          </div>
                          <div className={classes.inputGroup}>
                            <input
                              type="password"
                              value={signUpPassword}
                              onChange={(e) =>
                                setSignUpPassword(e.target.value)
                              }
                              onBlur={() => {
                                hasSignUpValidationErrors();
                              }}
                              id="register-password"
                              placeholder="password"
                              autoComplete="new-password"
                              data-test-id="signup-password"
                            />
                            {signUpValidationErrors.password && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="signup-password-error"
                              >
                                Password cannot be empty.
                              </div>
                            )}
                          </div>
                          <div className={classes.inputGroup}>
                            <input
                              type="password"
                              value={signUpConfirmPassword}
                              onChange={(e) =>
                                setSignUpConfirmPassword(e.target.value)
                              }
                              onBlur={() => {
                                hasSignUpValidationErrors();
                              }}
                              id="register-confirmpassword"
                              placeholder="confirm password"
                              autoComplete="new-password"
                              data-test-id="signup-confirm-password"
                            />
                            {signUpValidationErrors.confirmPassword && (
                              <div
                                className={classes.errorMessage}
                                data-test-id="signup-confirm-password-error"
                              >
                                Confirm password cannot be empty and must match
                                to the initial password.
                              </div>
                            )}
                          </div>
                          <button
                            className={
                              isSubmitted
                                ? classes.buttonDisabled
                                : classes.button
                            }
                            data-test-id="signup-button"
                            type="submit"
                          >
                            Submit
                          </button>
                        </Form>
                      </Formik>
                    </>
                  )}
                  {isSignUpSuccess && (
                    <>
                      <p
                        className={classes.info}
                        data-test-id="signup-success-message"
                      >
                        {' '}
                        We have sent a verification link to your email. Please
                        follow the instructions in the email to complete the
                        sign up process.
                      </p>
                    </>
                  )}
                </div>
              </div>
            </div>
          </div>
        </>
      )}
      {isForgotPassword && (
        <>
          <ForgotPassword setIsForgotPassword={setIsForgotPassword} />
        </>
      )}
    </>
  );
};
