"use client"

import { cx } from "class-variance-authority"
import { OTPInput, type SlotProps } from "input-otp"
import { useParams, useRouter } from "next/navigation"
import { type ReactNode, useState, useTransition } from "react"
import { useIntl } from "react-intl"

import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"

import { trpc } from "@/lib/trpc/client"

import Link from "@/components/TempDesignSystem/Link"

import { GenericError } from "../components/GenericError"
import { SASModal, SASModalContactBlock } from "../components/SASModal"
import Loading from "./loading"

import styles from "./OneTimePasswordForm.module.css"

import type { RequestOtpError } from "@/server/routers/partners/sas/otp/request/requestOtpError"
import type { OtpError } from "./page"

type Redirect = { url: string; type?: "replace" | "push" }
export type OnSubmitHandler = (args: { otp: string }) => Promise<Redirect>

export default function OneTimePasswordForm({
  heading,
  ingress,
  footnote,
  otpLength,
  onSubmit,
  error,
}: {
  heading: string
  ingress: string | ReactNode
  footnote?: string | ReactNode
  otpLength: number
  onSubmit: OnSubmitHandler
  error?: OtpError
}) {
  const router = useRouter()
  const params = useParams()
  const [isPending, startTransition] = useTransition()
  const [disableResend, setDisableResend] = useState(false)
  const intl = useIntl()
  const [otp, setOtp] = useState("")

  const requestOtp = trpc.partner.sas.requestOtp.useMutation({})

  if (requestOtp.isPending || isPending) {
    return <Loading />
  }

  if (requestOtp.isError) {
    const cause = requestOtp.error?.data?.cause as RequestOtpError

    const title = intl.formatMessage({
      defaultMessage: "Error requesting OTP",
    })
    const body = getRequestErrorBody(intl, cause?.errorCode)

    return (
      <GenericError title={title}>
        <Typography variant="Body/Paragraph/mdRegular">
          <p style={{ textAlign: "center" }}>{body}</p>
        </Typography>
        <SASModalContactBlock />
      </GenericError>
    )
  }

  switch (requestOtp.data?.status) {
    case "ABUSED":
    case "NOTSENT":
      router.push(`/${params.lang}/sas-x-scandic/error?errorCode=tooManyCodes`)
      return <Loading />
    case "NULL":
    case "RETRY":
    case "EXPIRED":
      // These errors should never happen for request, but according to the API spec they can
      throw new Error(`Unhandled request OTP status ${requestOtp.data?.status}`)
  }

  function handleRequestNewOtp(event: React.MouseEvent) {
    event.preventDefault()
    if (disableResend) return

    setOtp("")
    requestOtp.reset()
    requestOtp.mutate()
    setDisableResend(true)

    setTimeout(() => {
      setDisableResend(false)
    }, 15_000)
  }

  function handleOTPEntered(otp: string) {
    startTransition(async () => {
      const redirectRes = await onSubmit({ otp })
      setOtp("")
      if (redirectRes.type === "replace") {
        router.replace(redirectRes.url)
        return
      }

      router.push(redirectRes.url)
    })
  }

  const getResendOtpLink = (str: ReactNode) => (
    <Link
      href="#"
      onClick={handleRequestNewOtp}
      color="red"
      size="small"
      className={disableResend ? styles["disabled-link"] : ""}
    >
      {str}
    </Link>
  )

  const errorMessages: Record<OtpError, ReactNode> = {
    invalidCode: intl.formatMessage({
      defaultMessage: "The code you've entered is incorrect.",
    }),
    expiredCode: intl.formatMessage(
      {
        defaultMessage:
          "This code has expired. <resendOtpLink>Send new code.</resendOtpLink>",
      },
      {
        resendOtpLink: getResendOtpLink,
      }
    ),
  }

  const errorMessage = error ? errorMessages[error] : undefined

  return (
    <SASModal>
      <Typography variant="Title/Subtitle/lg">
        <h1>{heading}</h1>
      </Typography>
      <Typography variant="Body/Paragraph/mdRegular">
        <p>{ingress}</p>
      </Typography>

      <OTPInput
        autoFocus
        value={otp}
        onChange={setOtp}
        maxLength={otpLength}
        inputMode="numeric"
        onComplete={(otp) => {
          handleOTPEntered(otp)
        }}
        containerClassName={cx(styles["otp-container"], {
          [styles.error]: Boolean(error),
        })}
        render={({ slots }) => (
          <>
            {slots.map((slot, idx) => (
              <Slot key={idx} {...slot} />
            ))}
          </>
        )}
      />
      {errorMessage && (
        <div className={styles["error-message"]}>
          <MaterialIcon
            icon="cancel"
            isFilled
            size={20}
            color="Icon/Feedback/Error"
          />
          <Typography variant="Body/Supporting text (caption)/smRegular">
            <p style={{ color: "var(--Scandic-Red-60)" }}>{errorMessage}</p>
          </Typography>
        </div>
      )}
      <div>
        <Typography variant="Body/Supporting text (caption)/smRegular">
          <p>{footnote}</p>
        </Typography>
        <Typography variant="Body/Supporting text (caption)/smRegular">
          <p>
            {intl.formatMessage(
              {
                defaultMessage:
                  "Didn't receive a code? <resendOtpLink>Resend code</resendOtpLink>",
              },
              {
                resendOtpLink: getResendOtpLink,
              }
            )}
          </p>
        </Typography>
      </div>
    </SASModal>
  )
}

function Slot(props: SlotProps) {
  return (
    <div className={`${styles.slot} ${props.isActive ? styles.active : ""}`}>
      {props.char !== null && <div>{props.char}</div>}
      {props.hasFakeCaret && <FakeCaret />}
    </div>
  )
}

function FakeCaret() {
  return (
    <div className={styles.caret}>
      <div className={styles.child} />
    </div>
  )
}

const getRequestErrorBody = (
  intl: ReturnType<typeof useIntl>,
  errorCode: RequestOtpError["errorCode"]
) => {
  switch (errorCode) {
    case "TOO_MANY_REQUESTS":
      return intl.formatMessage({
        defaultMessage: "Too many requests. Please try again later.",
      })
    default:
      return intl.formatMessage({
        defaultMessage: "An error occurred while requesting a new OTP",
      })
  }
}
