import { useReducer, useEffect, useRef } from 'react';
import {
  Routes,
  Route,
  NavLink,
  useLocation,
  useNavigate,
  NavLinkProps,
} from 'react-router-dom';
import { SubmitHandler, useForm } from 'react-hook-form';

import { Auth } from '@aws-amplify/auth';

import { SignInForm, SignInFormValues } from './SignInForm';
import { SignUpForm, SignUpFormValues } from './SignUpForm';
import {
  ForgotPassword,
  ChangePasswordFormValues,
  ResetPasswordFormValues,
} from './ResetPasswordForm';
import { ConfirmationForm, ConfirmationFormValues } from './ConfirmationForm';
import {
  CompleteChallengeForm,
  ChallengeFormValues,
} from './CompleteChallengeForm';

import { Text, Spinner } from 'components/common';
import { Redirect } from 'components/router/Redirect';
import { RedirectTarget } from 'components/router/RedirectTarget';

import { sentryCaptureException } from 'lib/sentry';

const { NODE_ENV } = process.env;

const REQUIRED_COMPANY_NAME = 'Company name is required';
const PASSWORDS_DONT_MATCH = "Passwords don't match";
const PASSWORD_CHANGED = 'Your password has been changed successfully!';
const CODE_SENT = 'Verification code was sent to your email';
const EMAIL_VERIFIED = "Congratulations! You've just verified your email";

const AuthNavLink = (props: NavLinkProps) => {
  return (
    <NavLink
      className={({ isActive }) => {
        return `link-btn marginX2 fz24 bold tdn ${
          isActive ? 'c-darkblue' : 'c-gray'
        }`;
      }}
      {...props}
    />
  );
};

export const TEMP_USERNAME = 'tempUsername';

export const PATHS = {
  signin: 'signin',
  signup: 'signup',
  complete: 'complete',
  confirm: 'confirm',
  reset: 'reset',
  logout: 'logout',
};

type ReducerState = {
  loading: boolean;
  error: boolean;
  message: string | null;
};

type ReducerAction =
  | { type: 'REQUEST' }
  | { type: 'SUCCESS'; message: string }
  | { type: 'FAILURE'; message: string }
  | { type: 'RESET' };

function reducer(state: ReducerState, action: ReducerAction): ReducerState {
  switch (action.type) {
    case 'REQUEST':
      return { loading: true, error: false, message: null };
    case 'SUCCESS':
      return {
        loading: false,
        error: false,
        message: action.message,
      };
    case 'FAILURE':
      return {
        loading: false,
        error: true,
        message: action.message,
      };
    case 'RESET':
      return { ...state, loading: false, error: false };
    default:
      return state;
  }
}

export const AuthenticationPage = () => {
  const location = useLocation();
  const navigate = useNavigate();

  const userRef = useRef(null);

  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    error: false,
    message: null,
  });

  const { register, handleSubmit } = useForm<any>();

  useEffect(() => {
    dispatch({ type: 'RESET' });
  }, [location.pathname]);

  const handleSuccess = (res: any) => {
    if (NODE_ENV === 'development') {
      console.log(res);
    }
    return res;
  };

  const handleError = (error: any, data: any) => {
    console.log(error);
    sentryCaptureException(error, null, { email: data.username });

    dispatch({ type: 'FAILURE', message: error.message || error });
  };

  const signup: SubmitHandler<SignUpFormValues> = (data, ev) => {
    ev?.preventDefault();

    const { company, username, password, confirmPassword } = data;

    if (!company.trim()) {
      dispatch({ type: 'FAILURE', message: REQUIRED_COMPANY_NAME });
      return;
    }

    if (password !== confirmPassword) {
      dispatch({ type: 'FAILURE', message: PASSWORDS_DONT_MATCH });
      return;
    }

    dispatch({ type: 'REQUEST' });

    Auth.signUp({
      username: username.trim(),
      password: password.trim(),
      attributes: {
        email: username.trim(),
        'custom:company': company.trim(),
      },
    })
      .then(handleSuccess)
      .then(() => {
        dispatch({
          type: 'SUCCESS',
          message: CODE_SENT,
        });
      })
      .then(() => {
        navigate(`../${PATHS.confirm}`);
      })
      .catch((err) => handleError(err, data));
  };

  const confirm: SubmitHandler<ConfirmationFormValues> = (data, ev) => {
    ev?.preventDefault();

    const { username, password, code } = data;

    dispatch({ type: 'REQUEST' });

    Auth.confirmSignUp(username, code)
      .then(handleSuccess)
      .then(() => {
        if (username && password) {
          signin({ username, password }, ev);
        } else {
          dispatch({
            type: 'SUCCESS',
            message: EMAIL_VERIFIED,
          });
          navigate(`../${PATHS.signin}`);
        }
      })
      .catch((err) => handleError(err, data));
  };

  const resend: SubmitHandler<ConfirmationFormValues> = (data, ev) => {
    ev?.preventDefault();

    const { username } = data;

    dispatch({ type: 'REQUEST' });

    Auth.resendSignUp(username)
      .then(handleSuccess)
      .then(() => {
        dispatch({
          type: 'SUCCESS',
          message: CODE_SENT,
        });
      })
      .catch((err) => handleError(err, data));
  };

  const signin: SubmitHandler<SignInFormValues> = (data, ev) => {
    ev?.preventDefault();

    const { username, password } = data;

    dispatch({ type: 'REQUEST' });

    Auth.signIn({
      username: username.trim(),
      password: password.trim(),
    })
      .then(handleSuccess)
      .then((user) => {
        if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
          userRef.current = user;
          navigate(`../${PATHS.complete}?change=password`);
        }
      })
      .catch((err) => handleError(err, data));
  };

  const completeChallenge: SubmitHandler<ChallengeFormValues> = (data, ev) => {
    ev?.preventDefault();

    const { password, confirmPassword } = data;

    if (password !== confirmPassword) {
      dispatch({ type: 'FAILURE', message: PASSWORDS_DONT_MATCH });
      return;
    }

    dispatch({ type: 'REQUEST' });

    Auth.completeNewPassword(userRef.current, password)
      .then(handleSuccess)
      .catch((err) => handleError(err, data));
  };

  const forgotPassword: SubmitHandler<ResetPasswordFormValues> = (data, ev) => {
    ev?.preventDefault();

    const { username } = data;

    dispatch({ type: 'REQUEST' });

    Auth.forgotPassword(username)
      .then(handleSuccess)
      .then(() => {
        dispatch({
          type: 'SUCCESS',
          message: CODE_SENT,
        });
      })
      .then(() => {
        sessionStorage.setItem(TEMP_USERNAME, username);
        navigate(`../${PATHS.reset}?verify=email`);
      })
      .catch((err) => handleError(err, data));
  };

  const forgotPasswordSubmit: SubmitHandler<ChangePasswordFormValues> = (
    data,
    ev
  ) => {
    ev?.preventDefault();

    const { username, verificationCode, newPassword, confirmPassword } = data;
    const tempUsername = sessionStorage.getItem(TEMP_USERNAME);

    if (newPassword !== confirmPassword) {
      dispatch({ type: 'FAILURE', message: PASSWORDS_DONT_MATCH });
      return;
    }

    dispatch({ type: 'REQUEST' });

    // TODO refactor username || tempUsername
    Auth.forgotPasswordSubmit(
      username || tempUsername!,
      verificationCode,
      newPassword
    )
      .then(handleSuccess)
      .then(() => {
        dispatch({
          type: 'SUCCESS',
          message: PASSWORD_CHANGED,
        });
      })
      .then(() => {
        sessionStorage.removeItem(TEMP_USERNAME);
        navigate(`../${PATHS.signin}`);
      })
      .catch((err) => handleError(err, data));
  };

  return (
    <div className="flex alignCenter justifyCenter w100 h100vh">
      <div
        className="tac"
        style={{
          width: 360,
          ...(state.loading && { opacity: '.5', pointerEvents: 'none' }),
        }}
      >
        <div className="marginY4">
          <AuthNavLink
            to={{ pathname: `../${PATHS.signin}`, search: location.search }}
          >
            Sign In
          </AuthNavLink>
          <AuthNavLink
            to={{ pathname: `../${PATHS.signup}`, search: location.search }}
          >
            Sign Up
          </AuthNavLink>
        </div>

        {state.message && (
          <Text
            size="s"
            className={`mb4 rounded2 inline paddingX2 mark--${
              state.error ? 'red' : 'green'
            }`}
          >
            {state.message}
          </Text>
        )}

        <Routes>
          <Route
            path={PATHS.signin}
            element={
              <SignInForm submit={handleSubmit(signin)} register={register} />
            }
          />
          <Route
            path={PATHS.complete}
            element={
              userRef.current ? (
                <CompleteChallengeForm
                  submit={handleSubmit(completeChallenge)}
                  register={register}
                />
              ) : (
                <Redirect to={`../${PATHS.signin}`} />
              )
            }
          />
          <Route
            path={PATHS.signup}
            element={
              <SignUpForm submit={handleSubmit(signup)} register={register} />
            }
          />
          <Route
            path={PATHS.confirm}
            element={
              <ConfirmationForm
                resend={handleSubmit(resend)}
                submit={handleSubmit(confirm)}
                register={register}
              />
            }
          />
          <Route
            path={PATHS.reset}
            element={
              <ForgotPassword
                reset={handleSubmit(forgotPassword)}
                submit={handleSubmit(forgotPasswordSubmit)}
                register={register}
              />
            }
          />
          <Route path="*" element={<RedirectTarget to={PATHS.signin} />} />
        </Routes>
      </div>
      <Spinner show={state.loading} />
    </div>
  );
};
