import clsx from 'clsx';
import { h, Fragment, JSX } from 'preact';
import { useState, useRef } from 'preact/hooks';
import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';

import { setUser } from 'store/user/actions';
import { Input } from 'components/input';
import { TelegramLink } from 'components/telegram/telegram-link';
import { CrossIcon } from 'components/icons/cross';
import { TextareaAutosize } from 'components/textarea-autosize';
import { Spinner } from 'components/spinner/spinner';
import { ArrowIcon } from 'components/icons/arrow';

import { Button } from './components/button';
import { OAuth } from './components/oauth';
import { messages } from './auth.messsages';
import { useDropdown, useErrorMessage } from './auth.hooks';
import { getProviders, getTokenInvalidReason, persistEmail } from './auth.utils';
import {
  oauthSignin,
  emailSignin,
  verifyEmailSignin,
  anonymousSignin,
  verifyTelegramSignin,
  getTelegramSigninParams,
} from './auth.api';

import styles from './auth.module.css';

export function Auth() {
  const intl = useIntl();
  const telegramParamsRef = useRef<null | { bot: string; token: string }>(null);
  const emailRef = useRef('');
  const dispatch = useDispatch();
  const [oauthProviders, formProviders] = getProviders();

  // UI State
  const [isLoading, setLoading] = useState(false);
  const [view, setView] = useState<typeof formProviders[number] | 'token' | 'telegram'>(formProviders[0]);
  const [ref, isDropdownShown, toggleDropdownState] = useDropdown(view === 'token' || view === 'telegram');

  // Errors
  const [errorMessage, setError] = useErrorMessage();

  function handleClickSingIn(evt: Event) {
    evt.preventDefault();
    toggleDropdownState();
  }

  function resetView() {
    setView(formProviders[0]);
    setError(null);
  }

  function handleDropdownClose(evt: Event) {
    evt.preventDefault();
    resetView();
    toggleDropdownState();
  }

  function handleClickBack(evt: JSX.TargetedMouseEvent<HTMLButtonElement>) {
    evt.preventDefault();
    resetView();
  }

  async function handleOauthClick(evt: JSX.TargetedMouseEvent<HTMLAnchorElement>) {
    evt.preventDefault();

    const { href, dataset } = evt.currentTarget;

    if (dataset.providerName?.toLowerCase() === 'telegram') {
      try {
        telegramParamsRef.current = await getTelegramSigninParams();
      } catch (e) {
        setError(e);
        return;
      }

      setView('telegram');
      setError(null);
      return;
    }

    try {
      const user = await oauthSignin(href);

      if (user === null) {
        // TODO: add error message when user is null
        return;
      }
      dispatch(setUser(user));
    } catch (e) {
      setError(e);
    }
  }

  function handleProviderChange(evt: Event) {
    const { value } = evt.currentTarget as HTMLInputElement;

    setView(value as typeof formProviders[number]);
    setError(null);
  }

  async function handleSubmit(evt: Event) {
    const data = new FormData(evt.target as HTMLFormElement);

    evt.preventDefault();
    setLoading(true);
    setError(null);

    try {
      switch (view) {
        case 'anonymous': {
          const username = data.get('username') as string;
          const user = await anonymousSignin(username);

          dispatch(setUser(user));
          break;
        }
        case 'email': {
          const email = data.get('email') as string;
          const username = data.get('username') as string;

          emailRef.current = email;

          await emailSignin(email, username);
          setView('token');
          break;
        }
        case 'token': {
          const token = data.get('token') as string;
          const invalidReason = getTokenInvalidReason(token);

          if (invalidReason) {
            setError(invalidReason);
            break;
          }

          const user = await verifyEmailSignin(token);
          dispatch(setUser(user));
          persistEmail(emailRef.current);

          break;
        }
      }
    } catch (e) {
      setError(e);
    }

    setLoading(false);
  }

  async function handleTelegramSubmit(evt: Event) {
    evt.preventDefault();
    setLoading(true);
    setError(null);

    if (telegramParamsRef.current === null) {
      try {
        telegramParamsRef.current = await getTelegramSigninParams();
      } catch (e) {
        setError(e);
        return;
      }
    }

    try {
      const user = await verifyTelegramSignin(telegramParamsRef.current.token);

      dispatch(setUser(user));
    } catch (e) {
      setError(e);
    }

    setLoading(false);
  }

  function handleShowEmailStep(evt: Event) {
    evt.preventDefault();
    setView('email');
    setError(null);
  }

  const hasOAuthProviders = oauthProviders.length > 0;
  const hasFormProviders = formProviders.length > 0;

  const formFooterJSX = (
    <>
      {errorMessage && <div className={clsx('auth-error', styles.error)}>{errorMessage}</div>}
      <Button className="auth-submit" type="submit" disabled={isLoading}>
        {isLoading ? <Spinner /> : intl.formatMessage(messages.submit)}
      </Button>
    </>
  );

  return (
    <div className={clsx('auth', styles.root)}>
      <Button className="auth-button" selected={isDropdownShown} onClick={handleClickSingIn} suffix={<ArrowIcon />}>
        {intl.formatMessage(messages.signin)}
      </Button>
      {isDropdownShown && (
        <div className={clsx('auth-dropdown', styles.dropdown)} ref={ref}>
          <form className={clsx('auth-form', styles.form)} onSubmit={handleSubmit}>
            {view === 'telegram' && telegramParamsRef.current !== null ? (
              <>
                <div className={clsx('auth-row', styles.row)}>
                  <div className={styles.backButton}>
                    <Button className="auth-back-button" size="xs" kind="transparent" onClick={handleClickBack}>
                      <svg
                        className={styles.backButtonArrow}
                        width="14"
                        height="14"
                        viewBox="0 0 14 14"
                        fill="none"
                        xmlns="http://www.w3.org/2000/svg"
                      >
                        <path
                          d="M8.75 3L5 7.25L9 11"
                          stroke="currentColor"
                          stroke-width="2"
                          stroke-linecap="round"
                          stroke-linejoin="round"
                        />
                      </svg>
                      {intl.formatMessage(messages.back)}
                    </Button>
                  </div>
                  <button
                    className={clsx('auth-close-button', styles.closeButton)}
                    title="Close sign-in dropdown"
                    onClick={handleDropdownClose}
                  >
                    <CrossIcon />
                  </button>
                </div>
                <TelegramLink
                  onSubmit={handleTelegramSubmit}
                  errorMessage={errorMessage}
                  bot={telegramParamsRef.current.bot}
                  token={telegramParamsRef.current.token}
                />
              </>
            ) : view === 'token' ? (
              <>
                <div className={clsx('auth-row', styles.row)}>
                  <div className={styles.backButton}>
                    <Button className="auth-back-button" size="xs" kind="transparent" onClick={handleShowEmailStep}>
                      <svg
                        className={styles.backButtonArrow}
                        width="14"
                        height="14"
                        viewBox="0 0 14 14"
                        fill="none"
                        xmlns="http://www.w3.org/2000/svg"
                      >
                        <path
                          d="M8.75 3L5 7.25L9 11"
                          stroke="currentColor"
                          stroke-width="2"
                          stroke-linecap="round"
                          stroke-linejoin="round"
                        />
                      </svg>
                      {intl.formatMessage(messages.back)}
                    </Button>
                  </div>
                  <button
                    className={clsx('auth-close-button', styles.closeButton)}
                    title="Close sign-in dropdown"
                    onClick={handleDropdownClose}
                  >
                    <CrossIcon />
                  </button>
                </div>
                <div className={clsx('auth-row', styles.row)}>
                  <TextareaAutosize
                    name="token"
                    className={clsx('auth-token-textarea', styles.textarea)}
                    placeholder={intl.formatMessage(messages.token)}
                    disabled={isLoading}
                  />
                </div>
                {formFooterJSX}
              </>
            ) : (
              <>
                {hasOAuthProviders && (
                  <>
                    <h5 className={clsx('auth-form-title', styles.title)}>
                      {intl.formatMessage(messages.oauthSource)}
                    </h5>
                    <OAuth providers={oauthProviders} onOauthClick={handleOauthClick} />
                  </>
                )}
                {hasOAuthProviders && hasFormProviders && (
                  <div className={clsx('auth-divider', styles.divider)} title={intl.formatMessage(messages.or)} />
                )}
                {hasFormProviders && (
                  <>
                    {formProviders.length === 1 ? (
                      <h5 className={clsx('auth-form-title', styles.title)}>{formProviders[0]}</h5>
                    ) : (
                      <div className={clsx('auth-tabs', styles.tabs)}>
                        {formProviders.map((p) => (
                          <Fragment key={p}>
                            <input
                              className={styles.radio}
                              type="radio"
                              id={`form-provider-${p}`}
                              name="form-provider"
                              value={p}
                              onChange={handleProviderChange}
                              checked={p === view}
                            />
                            <label className={clsx('auth-tabs-item', styles.provider)} htmlFor={`form-provider-${p}`}>
                              {p.slice(0, 6)}
                            </label>
                          </Fragment>
                        ))}
                      </div>
                    )}
                    <div className={clsx('auth-row', styles.row)}>
                      <Input
                        className="auth-input-username"
                        required
                        name="username"
                        minLength={3}
                        pattern="[\p{L}\d\s_]+"
                        title={intl.formatMessage(messages.usernameRestriction)}
                        placeholder={intl.formatMessage(messages.username)}
                        disabled={isLoading}
                        onBlur={(evt) => {
                          const element = evt.target as HTMLInputElement;
                          element.value = element.value.trim();
                        }}
                      />
                    </div>
                    {view === 'email' && (
                      <div className={clsx('auth-row', styles.row)}>
                        <Input
                          className="auth-input-email"
                          required
                          name="email"
                          type="email"
                          placeholder={intl.formatMessage(messages.emailAddress)}
                          disabled={isLoading}
                        />
                      </div>
                    )}
                    <input className={styles.honeypot} type="checkbox" tabIndex={-1} autoComplete="off" />
                    {formFooterJSX}
                  </>
                )}
              </>
            )}
          </form>
        </div>
      )}
    </div>
  );
}
