import { Form, useStoreState } from "@ariakit/react";
import { type Dispatch, type SetStateAction, useState } from "react";
import { Link } from "wouter";
import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx";
import { cx } from "../class-names.ts";
import { interactiveText } from "../common.css.ts";
import { getSubmitFailureMessage } from "../failureMessages.ts";
import {
  Letterhead,
  LetterheadHeading,
  LetterheadParagraph,
  LetterheadSubmitButton,
} from "../Letterhead/index.tsx";
import { button } from "../Letterhead/style.css.ts";
import {
  LetterheadFormActions,
  LetterheadHeader,
  LetterheadSubmitError,
  LetterheadTextField,
} from "../LetterheadForm/index.tsx";
import { useForm } from "../use-form.ts";
import { validEmail, validPassword } from "../validations.ts";
import type { DefaultFormValues } from "./types.ts";

type SetStep = Dispatch<SetStateAction<ResetPasswordStep>>;

function RequestPasswordResetStep(props: {
  defaultValues?: DefaultFormValues;
  setStep: SetStep;
}) {
  const { defaultValues } = props;
  const { client, placeholders, hrefs } = useAppConfig();

  const { form, submitName } = useForm({
    defaultValues: {
      email: defaultValues?.email ?? "",
    },
    validate: {
      email: validEmail,
    },

    async onSubmit({ values }) {
      const op = await client.requestPasswordReset(values);

      return op.mapFailure((failure) => {
        return getSubmitFailureMessage(failure, {
          404: "🤔 Couldn't find a user with this email address.",
        });
      });
    },

    onSuccess(response, { values }) {
      props.setStep({
        type: "SUBMIT_CODE",
        tokenId: response.tokenId,
        email: values.email,
      });
    },
  });

  const emailValue = useStoreState(form, (s) => s.values.email);

  return (
    <Letterhead>
      <LetterheadHeader>
        <LetterheadHeading>Password reset</LetterheadHeading>
        <LetterheadParagraph>
          Enter your Indie Tabletop Club account email to begin password reset.
          We will send you a one-time code to verify your access.
        </LetterheadParagraph>
      </LetterheadHeader>

      <Form store={form} resetOnSubmit={false}>
        <LetterheadTextField
          name={form.names.email}
          label="Email"
          type="email"
          placeholder={placeholders.email}
          required
        />

        <LetterheadFormActions>
          <LetterheadSubmitError name={submitName} />
          <LetterheadSubmitButton>Continue</LetterheadSubmitButton>

          <LetterheadParagraph align="start">
            {"Remembered your password? "}
            <Link
              href={hrefs.login()}
              className={interactiveText}
              state={{ emailValue }}
            >
              Log in
            </Link>
            {"."}
          </LetterheadParagraph>
        </LetterheadFormActions>
      </Form>
    </Letterhead>
  );
}

function SubmitCodeStep(props: {
  tokenId: string;
  email: string;
  setStep: SetStep;
}) {
  const client = useClient();
  const { form, submitName } = useForm({
    defaultValues: {
      code: "",
    },

    async onSubmit({ values }) {
      const op = await client.checkPasswordResetCode({
        ...values,
        tokenId: props.tokenId,
      });

      return op.mapFailure((failure) => {
        return getSubmitFailureMessage(failure, {
          404: "🚫 This code is incorrect or expired. Please try again.",
        });
      });
    },

    onSuccess(_, { values }) {
      props.setStep({
        type: "SET_NEW_PASSWORD",
        tokenId: props.tokenId,
        code: values.code,
        email: props.email,
      });
    },
  });

  return (
    <Letterhead>
      <LetterheadHeader>
        <LetterheadHeading>Submit code</LetterheadHeading>
        <LetterheadParagraph>
          We've sent a one-time code to the email address you have provided.
          Please, enter the code in the field below to continue.
        </LetterheadParagraph>
      </LetterheadHeader>

      <Form store={form} resetOnSubmit={false}>
        <LetterheadTextField
          name={form.names.code}
          label="Code"
          placeholder="E.g. 123123"
          autoComplete="one-time-code"
          required
        />

        <LetterheadFormActions>
          <LetterheadSubmitError name={submitName} />
          <LetterheadSubmitButton>Verify code</LetterheadSubmitButton>
        </LetterheadFormActions>
      </Form>
    </Letterhead>
  );
}

function SetNewPasswordStep(props: {
  tokenId: string;
  code: string;
  email: string;
  setStep: SetStep;
}) {
  const client = useClient();

  const { form, submitName } = useForm({
    defaultValues: { password: "" },
    validate: { password: validPassword },
    async onSubmit({ values }) {
      const res = await client.setNewPassword({
        tokenId: props.tokenId,
        code: props.code,
        password: values.password,
      });

      if (res.isFailure) {
        return res.mapFailure((failure) => {
          return getSubmitFailureMessage(failure, {
            404: "⏱️ One-time code has expired. Please restart the password reset process.",
          });
        });
      }

      // Login attempt with new credentials. Must be performed in onSubmit so
      // that errors are correctly propagated.
      const loginOp = await client.login({
        email: props.email,
        password: values.password,
      });

      return loginOp.mapFailure(getSubmitFailureMessage);
    },
    onSuccess() {
      props.setStep({ type: "SUCCESS" });
    },
  });

  return (
    <Letterhead>
      <LetterheadHeader>
        <LetterheadHeading>Choose New Password</LetterheadHeading>
        <LetterheadParagraph>
          Please choose a new password. Make it at least 8 characters long.
        </LetterheadParagraph>
      </LetterheadHeader>

      <Form store={form} resetOnSubmit={false}>
        <LetterheadTextField
          name={form.names.password}
          label="New Password"
          type="password"
          placeholder="Enter a new password"
          required
        />

        <LetterheadFormActions>
          <LetterheadSubmitError name={submitName} />
          <LetterheadSubmitButton>Save & Login</LetterheadSubmitButton>
        </LetterheadFormActions>
      </Form>
    </Letterhead>
  );
}

function SuccessStep() {
  const { hrefs } = useAppConfig();

  return (
    <Letterhead>
      <LetterheadHeader>
        <LetterheadHeading>Success!</LetterheadHeading>
        <LetterheadParagraph>
          Your password has been successfully reset and you've been
          automatically logged in. Yay!
        </LetterheadParagraph>
      </LetterheadHeader>

      <LetterheadFormActions>
        <Link href={hrefs.dashboard()} {...cx(button())}>
          Go to dashboard
        </Link>
      </LetterheadFormActions>
    </Letterhead>
  );
}

type ResetPasswordStep =
  | { type: "REQUEST_PASSWORD_RESET" }
  | { type: "SUBMIT_CODE"; tokenId: string; email: string }
  | { type: "SET_NEW_PASSWORD"; tokenId: string; code: string; email: string }
  | { type: "SUCCESS" };

export type PasswordResetCardProps = {
  /**
   * Default values for the initial request password reset step.
   *
   * You might want to provide a value for history state or query param, so that
   * if a user jumps between login and password reset, their email address
   * is maintained between the two locations.
   */
  defaultValues?: DefaultFormValues;
};

/**
 * Allows the user to reset their password.
 */
export function PasswordResetCard(props: PasswordResetCardProps) {
  const [step, setStep] = useState<ResetPasswordStep>({
    type: "REQUEST_PASSWORD_RESET",
  });

  switch (step.type) {
    case "REQUEST_PASSWORD_RESET": {
      return <RequestPasswordResetStep {...props} setStep={setStep} />;
    }

    case "SUBMIT_CODE": {
      return <SubmitCodeStep {...step} setStep={setStep} />;
    }

    case "SET_NEW_PASSWORD": {
      return <SetNewPasswordStep {...step} setStep={setStep} />;
    }

    case "SUCCESS": {
      return <SuccessStep />;
    }
  }
}
