import { cookies } from "next/headers"
import { redirect } from "next/navigation"
import { z } from "zod"

import { myPages } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server"

import { getIntl } from "@/i18n"
import { safeTry } from "@/utils/safeTry"

import { SAS_TOKEN_STORAGE_KEY, SAS_TRANSFER_POINT_KEY } from "../sasUtils"
import OneTimePasswordForm, {
  type OnSubmitHandler,
} from "./OneTimePasswordForm"

import type { ReactNode } from "react"

import type { LangParams, PageArgs, SearchParams } from "@/types/params"
import type { Lang } from "@/constants/languages"

const otpError = z.enum(["invalidCode", "expiredCode"])
const intent = z.enum(["link", "unlink", "transfer"])
const searchParamsSchema = z.object({
  intent: intent,
  to: z.string(),
  error: otpError.optional(),
})

export type OtpError = z.infer<typeof otpError>
type Intent = z.infer<typeof intent>

export default async function SASxScandicOneTimePasswordPage(
  props: PageArgs<LangParams> & SearchParams
) {
  const params = await props.params
  const searchParams = await props.searchParams
  const intl = await getIntl()
  const cookieStore = await cookies()
  const tokenCookie = cookieStore.get(SAS_TOKEN_STORAGE_KEY)

  const result = searchParamsSchema.safeParse(searchParams)
  if (!result.success) {
    throw new Error("Invalid search params")
  }
  const { intent, to, error } = result.data

  if (!verifyTokenValidity(tokenCookie?.value)) {
    redirect(`/${params.lang}/sas-x-scandic/login?intent=${intent}`)
  }

  const handleOtpVerified: OnSubmitHandler = async ({ otp }) => {
    "use server"
    const caller = await serverClient()
    const [data, error] = await safeTry(caller.partner.sas.verifyOtp({ otp }))

    if (error || !data) {
      throw error || new Error("OTP verification failed")
    }

    switch (data.status) {
      case "ABUSED":
        return {
          url: `/${params.lang}/sas-x-scandic/error?errorCode=tooManyFailedAttempts`,
        }
      case "EXPIRED": {
        const search = new URLSearchParams({
          ...searchParams,
          error: "expiredCode",
        }).toString()

        return {
          url: `/${params.lang}/sas-x-scandic/otp?${search}`,
        }
      }
      case "RETRY": {
        const search = new URLSearchParams({
          ...searchParams,
          error: "invalidCode",
        }).toString()

        return {
          url: `/${params.lang}/sas-x-scandic/otp?${search}`,
        }
      }
      case "NOTSENT":
      case "NULL":
      case "SENT":
      case "PENDING":
        // These errors should never happen for verify, but according to the API spec they can
        throw new Error("Unhandled OTP status")
    }

    switch (intent) {
      case "link":
        return handleLinkAccount({ lang: params.lang })
      case "unlink":
        return handleUnlinkAccount({ lang: params.lang })
      case "transfer":
        return handleTransferPoints({ lang: params.lang })
    }
  }

  const maskedContactInfo = () => (
    <>
      <br />
      <strong>{to}</strong>
      <br />
    </>
  )
  const intentDescriptions: Record<Intent, ReactNode> = {
    link: intl.formatMessage(
      {
        defaultMessage:
          "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
      },
      { maskedContactInfo }
    ),
    unlink: intl.formatMessage(
      {
        defaultMessage:
          "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
      },
      { maskedContactInfo }
    ),
    transfer: intl.formatMessage(
      {
        defaultMessage:
          "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to transfer your points.",
      },
      { maskedContactInfo }
    ),
  }

  return (
    <OneTimePasswordForm
      heading={intl.formatMessage({
        defaultMessage: "Verification code",
      })}
      ingress={intentDescriptions[intent]}
      footnote={intl.formatMessage({
        defaultMessage: "This verification is needed for additional security.",
      })}
      otpLength={6}
      onSubmit={handleOtpVerified}
      error={error}
    />
  )
}

function verifyTokenValidity(token: string | undefined) {
  if (!token) {
    return false
  }

  try {
    const decoded = JSON.parse(atob(token.split(".")[1]))
    const expiry = decoded.exp * 1000
    return Date.now() < expiry
  } catch {
    return false
  }
}

async function handleLinkAccount({
  lang,
}: {
  lang: Lang
}): ReturnType<OnSubmitHandler> {
  const caller = await serverClient()
  const [res, error] = await safeTry(caller.partner.sas.linkAccount())
  if (!res || error) {
    console.error("[SAS] link account error", error)
    return {
      url: `/${lang}/sas-x-scandic/error`,
    }
  }

  switch (res.linkingState) {
    case "alreadyLinked":
      return {
        url: `/${lang}/sas-x-scandic/error?errorCode=alreadyLinked`,
        type: "replace",
      }
    case "linked":
      return {
        url: `/${lang}/sas-x-scandic/link/success`,
        type: "replace",
      }
    case "dateOfBirthMismatch":
      return {
        url: `/${lang}/sas-x-scandic/error?errorCode=dateOfBirthMismatch`,
        type: "replace",
      }
    // Currently unhandled errors
    case "accountToNew":
    case "nameNotMatched":
    case "blockedForRelink":
    case "error":
    default:
      return {
        url: `/${lang}/sas-x-scandic/error`,
        type: "replace",
      }
  }
}

async function handleUnlinkAccount({
  lang,
}: {
  lang: Lang
}): ReturnType<OnSubmitHandler> {
  const caller = await serverClient()
  const [res, error] = await safeTry(caller.partner.sas.unlinkAccount())
  if (!res || error) {
    console.error("[SAS] unlink account error", error)
    return {
      url: `/${lang}/sas-x-scandic/error`,
    }
  }

  switch (res.linkingState) {
    case "unlinked":
      return {
        url: `/${lang}/sas-x-scandic/unlink/success`,
        type: "replace",
      }
    case "notLinked":
      return {
        url: myPages[lang],
      }
    case "error":
      return {
        url: `/${lang}/sas-x-scandic/error?errorCode=unlinkError`,
        type: "replace",
      }
  }
}

async function handleTransferPoints({
  lang,
}: {
  lang: Lang
}): ReturnType<OnSubmitHandler> {
  const cookieStore = await cookies()
  const pointsCookie = cookieStore.get(SAS_TRANSFER_POINT_KEY)
  const points = Number(pointsCookie?.value)

  cookieStore.delete(SAS_TRANSFER_POINT_KEY)

  if (!pointsCookie || !points || isNaN(points)) {
    return {
      url: `/${lang}/sas-x-scandic/error`,
      type: "replace",
    }
  }

  const caller = await serverClient()
  const [res, error] = await safeTry(
    caller.partner.sas.transferPoints({
      points,
    })
  )

  if (!res || error) {
    console.error("[SAS] transfer points error", error)
    return {
      // TODO better errors?
      url: `/${lang}/sas-x-scandic/error`,
      type: "replace",
    }
  }

  console.log("[SAS] transfer points response", res)

  return {
    url: `/${lang}/sas-x-scandic/transfer/success?p=${points}`,
    type: "replace",
  }
}
