"use client"

import { zodResolver } from "@hookform/resolvers/zod"
import { cx } from "class-variance-authority"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useCallback, useEffect, useState } from "react"
import { Label } from "react-aria-components"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"

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

import {
  BookingStatusEnum,
  PAYMENT_METHOD_TITLES,
  PaymentMethodEnum,
  REDEMPTION,
} from "@/constants/booking"
import {
  bookingConfirmation,
  selectRate,
} from "@/constants/routes/hotelReservation"
import { env } from "@/env/client"
import { trpc } from "@/lib/trpc/client"
import { useEnterDetailsStore } from "@/stores/enter-details"

import PaymentOption from "@/components/HotelReservation/PaymentOption"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions"
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
import useLang from "@/hooks/useLang"
import useStickyPosition from "@/hooks/useStickyPosition"
import { trackPaymentEvent } from "@/utils/tracking"
import { trackEvent } from "@/utils/tracking/base"
import { trackGlaSaveCardAttempt } from "@/utils/tracking/myStay"

import { bedTypeMap } from "../../utils"
import ConfirmBooking, { ConfirmBookingRedemption } from "../Confirm"
import PriceChangeDialog from "../PriceChangeDialog"
import { writeGlaToSessionStorage } from "./PaymentCallback/helpers"
import BookingAlert from "./BookingAlert"
import GuaranteeDetails from "./GuaranteeDetails"
import { hasFlexibleRate, hasPrepaidRate, isPaymentMethodEnum } from "./helpers"
import MixedRatePaymentBreakdown from "./MixedRatePaymentBreakdown"
import PaymentOptionsGroup from "./PaymentOptionsGroup"
import { type PaymentFormData, paymentSchema } from "./schema"
import TermsAndConditions from "./TermsAndConditions"

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

import type {
  PaymentClientProps,
  PriceChangeData,
} from "@/types/components/hotelReservation/enterDetails/payment"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"

const maxRetries = 15
const retryInterval = 2000

export const formId = "submit-booking"

export default function PaymentClient({
  otherPaymentOptions,
  savedCreditCards,
  isUserLoggedIn,
}: PaymentClientProps) {
  const router = useRouter()
  const lang = useLang()
  const intl = useIntl()
  const pathname = usePathname()
  const searchParams = useSearchParams()
  const { getTopOffset } = useStickyPosition({})

  const [showBookingAlert, setShowBookingAlert] = useState(false)

  const {
    booking,
    rooms,
    totalPrice,
    isSubmitting,
    preSubmitCallbacks,
    setIsSubmitting,
  } = useEnterDetailsStore((state) => ({
    booking: state.booking,
    rooms: state.rooms,
    totalPrice: state.totalPrice,
    preSubmitCallbacks: state.preSubmitCallbacks,
    isSubmitting: state.isSubmitting,
    setIsSubmitting: state.actions.setIsSubmitting,
  }))

  const bookingMustBeGuaranteed = rooms.some(({ room }, idx) => {
    if (idx === 0 && isUserLoggedIn && room.memberMustBeGuaranteed) {
      return true
    }

    if (
      (room.guest.join || room.guest.membershipNo) &&
      booking.rooms[idx].counterRateCode
    ) {
      return room.memberMustBeGuaranteed
    }

    return room.mustBeGuaranteed
  })

  const setIsSubmittingDisabled = useEnterDetailsStore(
    (state) => state.actions.setIsSubmittingDisabled
  )

  const [refId, setRefId] = useState("")
  const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
    useState(false)

  const availablePaymentOptions =
    useAvailablePaymentOptions(otherPaymentOptions)
  const [priceChangeData, setPriceChangeData] =
    useState<PriceChangeData | null>(null)

  const { toDate, fromDate, hotelId } = booking

  const hasPrepaidRates = rooms.some(hasPrepaidRate)
  const hasFlexRates = rooms.some(hasFlexibleRate)
  const hasOnlyFlexRates = rooms.every(hasFlexibleRate)
  const hasMixedRates = hasPrepaidRates && hasFlexRates

  const methods = useForm<PaymentFormData>({
    defaultValues: {
      paymentMethod: savedCreditCards?.length
        ? savedCreditCards[0].id
        : PaymentMethodEnum.card,
      smsConfirmation: false,
      termsAndConditions: false,
      guarantee: false,
    },
    mode: "all",
    reValidateMode: "onChange",
    resolver: zodResolver(paymentSchema),
  })

  const initiateBooking = trpc.booking.create.useMutation({
    onSuccess: (result) => {
      if (result) {
        if ("error" in result) {
          const queryParams = new URLSearchParams(searchParams.toString())
          queryParams.set("errorCode", result.cause)
          window.history.replaceState(
            {},
            "",
            `${pathname}?${queryParams.toString()}`
          )
          handlePaymentError(result.cause)
          return
        }

        const mainRoom = result.rooms[0]

        if (result.reservationStatus == BookingStatusEnum.BookingCompleted) {
          const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(mainRoom.refId)}`
          router.push(confirmationUrl)
          return
        }

        setRefId(mainRoom.refId)

        const hasPriceChange = result.rooms.some((r) => r.priceChangedMetadata)
        if (hasPriceChange) {
          const priceChangeData = result.rooms.map(
            (room) => room.priceChangedMetadata || null
          )
          setPriceChangeData(priceChangeData)
        } else {
          setIsPollingForBookingStatus(true)
        }
      } else {
        handlePaymentError("No confirmation number")
      }
    },
    onError: (error) => {
      console.error("Error", error)
      handlePaymentError(error.message)
    },
  })

  const priceChange = trpc.booking.priceChange.useMutation({
    onSuccess: (result) => {
      if (result?.id) {
        setIsPollingForBookingStatus(true)
      } else {
        handlePaymentError("No confirmation number")
      }
      setPriceChangeData(null)
    },
    onError: (error) => {
      console.error("Error", error)
      setPriceChangeData(null)
      handlePaymentError(error.message)
    },
  })

  const bookingStatus = useHandleBookingStatus({
    refId,
    expectedStatuses: [BookingStatusEnum.BookingCompleted],
    maxRetries,
    retryInterval,
    enabled: isPollingForBookingStatus,
  })

  const handlePaymentError = useCallback(
    (errorMessage: string) => {
      setShowBookingAlert(true)
      setIsSubmitting(false)

      const currentPaymentMethod = methods.getValues("paymentMethod")
      const smsEnable = methods.getValues("smsConfirmation")
      const guarantee = methods.getValues("guarantee")
      const isSavedCreditCard = savedCreditCards?.some(
        (card) => card.id === currentPaymentMethod
      )

      if (guarantee || (bookingMustBeGuaranteed && hasOnlyFlexRates)) {
        const lateArrivalGuarantee = guarantee ? "yes" : "mandatory"
        trackEvent({
          event: "glaCardSaveFailed",
          hotelInfo: {
            hotelId,
            lateArrivalGuarantee,
            guaranteedProduct: "room",
          },
          paymentInfo: {
            isSavedCreditCard,
            hotelId,
            status: "glacardsavefailed",
          },
        })
      } else {
        trackPaymentEvent({
          event: "paymentFail",
          hotelId,
          method: currentPaymentMethod,
          isSavedCreditCard,
          smsEnable,
          errorMessage,
          status: "failed",
        })
      }
    },
    [
      methods,
      savedCreditCards,
      hotelId,
      bookingMustBeGuaranteed,
      hasOnlyFlexRates,
      setIsSubmitting,
    ]
  )

  useEffect(() => {
    if (bookingStatus?.data?.paymentUrl) {
      router.push(bookingStatus.data.paymentUrl)
    } else if (
      bookingStatus?.data?.reservationStatus ===
      BookingStatusEnum.BookingCompleted
    ) {
      const mainRoom = bookingStatus.data.rooms[0]
      const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(mainRoom.refId)}`
      router.push(confirmationUrl)
    } else if (bookingStatus.isTimeout) {
      handlePaymentError("Timeout")
    }
  }, [bookingStatus, router, intl, lang, handlePaymentError])

  useEffect(() => {
    setIsSubmittingDisabled(
      !methods.formState.isValid || methods.formState.isSubmitting
    )
  }, [
    methods.formState.isValid,
    methods.formState.isSubmitting,
    setIsSubmittingDisabled,
  ])

  const getPaymentMethod = useCallback(
    (paymentMethod: string | null | undefined): PaymentMethodEnum => {
      if (hasFlexRates) {
        return PaymentMethodEnum.card
      }
      return paymentMethod && isPaymentMethodEnum(paymentMethod)
        ? paymentMethod
        : PaymentMethodEnum.card
    },
    [hasFlexRates]
  )

  const handleSubmit = useCallback(
    (data: PaymentFormData) => {
      setIsSubmitting(true)

      Object.values(preSubmitCallbacks).forEach((callback) => {
        callback()
      })
      const firstIncompleteRoomIndex = rooms.findIndex(
        (room) => !room.isComplete
      )

      // If any room is not complete/valid, scroll to it
      if (firstIncompleteRoomIndex !== -1) {
        const roomElement = document.getElementById(
          `room-${firstIncompleteRoomIndex + 1}`
        )

        if (!roomElement) {
          return
        }
        const roomElementTop =
          roomElement.getBoundingClientRect().top + window.scrollY

        window.scrollTo({
          top: roomElementTop - getTopOffset() - 20,
          behavior: "smooth",
        })

        return
      }

      const paymentMethod = getPaymentMethod(data.paymentMethod)

      const savedCreditCard = savedCreditCards?.find(
        (card) => card.id === data.paymentMethod
      )

      const paymentRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}/${lang}/hotelreservation/payment-callback`
      const guarantee = data.guarantee
      const useSavedCard = savedCreditCard
        ? {
            card: {
              alias: savedCreditCard.alias,
              expiryDate: savedCreditCard.expirationDate,
              cardType: savedCreditCard.cardType,
            },
          }
        : {}

      const shouldUsePayment =
        guarantee || bookingMustBeGuaranteed || !hasOnlyFlexRates
      const payment = shouldUsePayment
        ? {
            paymentMethod: paymentMethod,
            ...useSavedCard,
            success: `${paymentRedirectUrl}/success`,
            error: `${paymentRedirectUrl}/error`,
            cancel: `${paymentRedirectUrl}/cancel`,
          }
        : undefined

      if (guarantee || (bookingMustBeGuaranteed && hasOnlyFlexRates)) {
        const lateArrivalGuarantee = guarantee ? "yes" : "mandatory"
        writeGlaToSessionStorage(lateArrivalGuarantee, hotelId)
        trackGlaSaveCardAttempt(hotelId, savedCreditCard, lateArrivalGuarantee)
      } else if (!hasOnlyFlexRates) {
        trackPaymentEvent({
          event: "paymentAttemptStart",
          hotelId,
          method: savedCreditCard ? savedCreditCard.type : paymentMethod,
          isSavedCreditCard: !!savedCreditCard,
          smsEnable: data.smsConfirmation,
          status: "attempt",
        })
      }

      const payload = {
        checkInDate: fromDate,
        checkOutDate: toDate,
        hotelId,
        language: lang,
        payment,
        rooms: rooms.map(({ room }, idx) => {
          const isMainRoom = idx === 0
          let rateCode = ""
          if (isMainRoom && isUserLoggedIn) {
            rateCode = booking.rooms[idx].rateCode
          } else if (
            (room.guest.join || room.guest.membershipNo) &&
            booking.rooms[idx].counterRateCode
          ) {
            rateCode = booking.rooms[idx].counterRateCode
          } else {
            rateCode = booking.rooms[idx].rateCode
          }
          return {
            adults: room.adults,
            bookingCode: room.roomRate.bookingCode,
            childrenAges: room.childrenInRoom?.map((child) => ({
              age: child.age,
              bedType: bedTypeMap[parseInt(child.bed.toString())],
            })),
            guest: {
              becomeMember: room.guest.join,
              countryCode: room.guest.countryCode,
              email: room.guest.email,
              firstName: room.guest.firstName,
              lastName: room.guest.lastName,
              membershipNumber: room.guest.membershipNo,
              phoneNumber: room.guest.phoneNumber,
              // Only allowed for room one
              ...(idx === 0 && {
                dateOfBirth:
                  "dateOfBirth" in room.guest && room.guest.dateOfBirth
                    ? room.guest.dateOfBirth
                    : undefined,
                postalCode:
                  "zipCode" in room.guest && room.guest.zipCode
                    ? room.guest.zipCode
                    : undefined,
              }),
            },
            packages: {
              accessibility:
                room.roomFeatures?.some(
                  (feature) =>
                    feature.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
                ) ?? false,
              allergyFriendly:
                room.roomFeatures?.some(
                  (feature) => feature.code === RoomPackageCodeEnum.ALLERGY_ROOM
                ) ?? false,
              breakfast: !!(room.breakfast && room.breakfast.code),
              petFriendly:
                room.roomFeatures?.some(
                  (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
                ) ?? false,
            },
            rateCode,
            roomPrice: {
              memberPrice:
                "member" in room.roomRate
                  ? room.roomRate.member?.localPrice.pricePerStay
                  : undefined,
              publicPrice:
                "public" in room.roomRate
                  ? room.roomRate.public?.localPrice.pricePerStay
                  : undefined,
            },
            roomTypeCode: room.bedType!.roomTypeCode, // A selection has been made in order to get to this step.
            smsConfirmationRequested: data.smsConfirmation,
            specialRequest: {
              comment: room.specialRequest.comment
                ? room.specialRequest.comment
                : undefined,
            },
          }
        }),
      }

      initiateBooking.mutate(payload)
    },
    [
      savedCreditCards,
      lang,
      initiateBooking,
      hotelId,
      fromDate,
      toDate,
      rooms,
      booking.rooms,
      getPaymentMethod,
      hasOnlyFlexRates,
      bookingMustBeGuaranteed,
      preSubmitCallbacks,
      isUserLoggedIn,
      getTopOffset,
      setIsSubmitting,
    ]
  )

  const paymentGuarantee = intl.formatMessage({
    defaultMessage: "Payment Guarantee",
  })
  const payment = intl.formatMessage({
    defaultMessage: "Payment",
  })
  const confirm = intl.formatMessage({
    defaultMessage: "Confirm booking",
  })

  return (
    <section
      className={cx(styles.paymentSection, {
        [styles.disabled]: isSubmitting,
      })}
    >
      <header>
        <Title level="h2" as="h4">
          {hasOnlyFlexRates && bookingMustBeGuaranteed
            ? paymentGuarantee
            : hasOnlyFlexRates
              ? confirm
              : payment}
        </Title>
        <BookingAlert isVisible={showBookingAlert} />
      </header>
      <FormProvider {...methods}>
        <form
          className={styles.paymentContainer}
          onSubmit={methods.handleSubmit(handleSubmit)}
          id={formId}
        >
          {booking.searchType === REDEMPTION ? (
            <ConfirmBookingRedemption />
          ) : hasOnlyFlexRates && !bookingMustBeGuaranteed ? (
            <ConfirmBooking savedCreditCards={savedCreditCards} />
          ) : (
            <>
              {hasOnlyFlexRates && bookingMustBeGuaranteed ? (
                <section className={styles.section}>
                  <Body>
                    {intl.formatMessage({
                      defaultMessage:
                        "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
                    })}
                  </Body>
                  <GuaranteeDetails />
                </section>
              ) : null}

              {hasMixedRates ? (
                <Body>
                  {intl.formatMessage({
                    defaultMessage:
                      "As your booking includes rooms with different terms, we will be charging part of the booking now and the remainder will be collected by the reception at check-in.",
                  })}
                </Body>
              ) : null}

              <section className={styles.section}>
                <PaymentOptionsGroup
                  name="paymentMethod"
                  className={styles.paymentOptionContainer}
                >
                  <Label className="sr-only">
                    {intl.formatMessage({
                      defaultMessage: "Payment methods",
                    })}
                  </Label>

                  {savedCreditCards?.length ? (
                    <>
                      <Typography variant="Title/Overline/sm">
                        <span>
                          {intl.formatMessage({
                            defaultMessage: "MY SAVED CARDS",
                          })}
                        </span>
                      </Typography>

                      {savedCreditCards.map((savedCreditCard) => (
                        <PaymentOption
                          key={savedCreditCard.id}
                          value={savedCreditCard.id}
                          label={
                            PAYMENT_METHOD_TITLES[
                              savedCreditCard.cardType as PaymentMethodEnum
                            ]
                          }
                          cardNumber={savedCreditCard.truncatedNumber}
                        />
                      ))}

                      <Typography variant="Title/Overline/sm">
                        <span>
                          {intl.formatMessage({
                            defaultMessage: "OTHER PAYMENT METHODS",
                          })}
                        </span>
                      </Typography>
                    </>
                  ) : null}
                  <PaymentOption
                    value={PaymentMethodEnum.card}
                    label={intl.formatMessage({
                      defaultMessage: "Credit card",
                    })}
                  />
                  {!hasMixedRates &&
                    availablePaymentOptions.map((paymentMethod) => (
                      <PaymentOption
                        key={paymentMethod}
                        value={paymentMethod}
                        label={
                          PAYMENT_METHOD_TITLES[
                            paymentMethod as PaymentMethodEnum
                          ]
                        }
                      />
                    ))}
                </PaymentOptionsGroup>
                {hasMixedRates ? (
                  <MixedRatePaymentBreakdown
                    rooms={rooms}
                    currency={totalPrice.local.currency}
                  />
                ) : null}
              </section>

              <section className={styles.section}>
                <TermsAndConditions isFlexBookingTerms={hasOnlyFlexRates} />
              </section>
            </>
          )}
          <div className={styles.submitButton}>
            <Button
              type="submit"
              isDisabled={methods.formState.isSubmitting}
              isPending={isSubmitting}
              typography="Body/Supporting text (caption)/smBold"
            >
              {intl.formatMessage({
                defaultMessage: "Complete booking",
              })}
            </Button>
          </div>
        </form>
      </FormProvider>
      {priceChangeData ? (
        <PriceChangeDialog
          isOpen={!!priceChangeData}
          priceChangeData={priceChangeData}
          prevTotalPrice={totalPrice.local.price}
          currency={totalPrice.local.currency}
          onCancel={() => {
            const allSearchParams = searchParams.size
              ? `?${searchParams.toString()}`
              : ""
            router.push(`${selectRate(lang)}${allSearchParams}`)
          }}
          onAccept={() => priceChange.mutate({ refId })}
        />
      ) : null}
    </section>
  )
}
