"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 { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"

import { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
import {
  bookingConfirmation,
  selectRate,
} from "@scandic-hotels/common/constants/routes/hotelReservation"
import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition"
import { logger } from "@scandic-hotels/common/logger"
import { formatPhoneNumber } from "@scandic-hotels/common/utils/phone"
import { Button } from "@scandic-hotels/design-system/Button"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trackEvent } from "@scandic-hotels/tracking/base"
import {
  trackGlaSaveCardAttempt,
  trackPaymentEvent,
} from "@scandic-hotels/tracking/payment"
import { trpc } from "@scandic-hotels/trpc/client"
import { bedTypeMap } from "@scandic-hotels/trpc/constants/bedTypeMap"
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
import { BookingStatusEnum } from "@scandic-hotels/trpc/enums/bookingStatus"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"

import { useBookingFlowConfig } from "../../../bookingFlowConfig/bookingFlowConfigContext"
import { useBookingFlowContext } from "../../../hooks/useBookingFlowContext"
import { clearBookingWidgetState } from "../../../hooks/useBookingWidgetState"
import { useHandleBookingStatus } from "../../../hooks/useHandleBookingStatus"
import useLang from "../../../hooks/useLang"
import { useEnterDetailsStore } from "../../../stores/enter-details"
import ConfirmBooking from "../Confirm"
import PriceChangeDialog from "../PriceChangeDialog"
import { writeGlaToSessionStorage } from "./PaymentCallback/helpers"
import BookingAlert from "./BookingAlert"
import { GuaranteeInfo } from "./GuaranteeInfo"
import {
  getPaymentData,
  getPaymentMethod,
  hasFlexibleRate,
  hasPrepaidRate,
  mustGuaranteeBooking,
  writePaymentInfoToSessionStorage,
} from "./helpers"
import { type PaymentFormData, paymentSchema } from "./schema"
import { getPaymentHeadingConfig } from "./utils"

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

import type { CreateBookingInput } from "@scandic-hotels/trpc/routers/booking/input"
import type { CreditCard } from "@scandic-hotels/trpc/types/user"

import type { PriceChangeData } from "../PriceChangeData"

const maxRetries = 15
const retryInterval = 2000

type PaymentClientProps = {
  otherPaymentOptions: PaymentMethodEnum[]
  savedCreditCards: CreditCard[] | null
}

export const formId = "submit-booking"
export default function PaymentClient({
  otherPaymentOptions,
  savedCreditCards,
}: PaymentClientProps) {
  const router = useRouter()
  const lang = useLang()
  const intl = useIntl()
  const pathname = usePathname()
  const searchParams = useSearchParams()
  const { getTopOffset } = useStickyPosition({})
  const { user, isLoggedIn } = useBookingFlowContext()
  const { redemptionType } = useBookingFlowConfig()
  const [refId, setRefId] = useState("")
  const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
    useState(false)
  const [priceChangeData, setPriceChangeData] =
    useState<PriceChangeData | null>(null)
  const [showBookingAlert, setShowBookingAlert] = useState(false)

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

  const bookingMustBeGuaranteed = mustGuaranteeBooking({
    isUserLoggedIn: isLoggedIn,
    booking,
    rooms,
  })

  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) {
        handlePaymentError("No confirmation number")
        return
      }

      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 { booking } = result
      const mainRoom = booking.rooms[0]

      if (booking.reservationStatus == BookingStatusEnum.BookingCompleted) {
        clearBookingWidgetState()
        // Cookie is used by Booking Confirmation page to validate that the user came from payment callback
        // eslint-disable-next-line react-hooks/immutability
        document.cookie = `bcsig=${result.sig}; Path=/; Max-Age=60; Secure; SameSite=Strict`
        const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(mainRoom.refId)}`
        router.push(confirmationUrl)
        return
      }

      setRefId(mainRoom.refId)

      const hasPriceChange = booking.rooms.some((r) => r.priceChangedMetadata)
      if (hasPriceChange) {
        const priceChangeData = booking.rooms
          .map((room) => room.priceChangedMetadata || null)
          .filter(isNotNull)
        setPriceChangeData(priceChangeData)
      } else {
        setIsPollingForBookingStatus(true)
      }
    },
    onError: (error) => {
      logger.error("Booking 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) => {
      logger.error("Price change error", error)
      setPriceChangeData(null)
      handlePaymentError(error.message)
    },
  })

  const { toDate, fromDate, hotelId } = booking
  const trackPaymentError = useCallback(
    (errorMessage: string) => {
      const currentPaymentMethod = methods.getValues("paymentMethod")
      const smsEnable = methods.getValues("smsConfirmation")
      const guarantee = methods.getValues("guarantee")
      const savedCreditCard = savedCreditCards?.find(
        (card) => card.id === currentPaymentMethod
      )

      const isSavedCreditCard = !!savedCreditCard

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

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

      trackPaymentError(errorMessage)
    },
    [setIsSubmitting, trackPaymentError]
  )

  useBookingStatusRedirect({
    refId,
    enabled: isPollingForBookingStatus,
    onError: handlePaymentError,
  })

  const scrollToInvalidField = useCallback(async (): Promise<boolean> => {
    // If any room is not complete/valid, scroll to the first invalid field, this is needed as rooms and other fields are in separate forms

    const invalidField = await runPreSubmitCallbacks()
    const errorNames = Object.keys(methods.formState.errors)
    const firstIncompleteRoomIndex = rooms.findIndex((room) => !room.isComplete)

    if (invalidField) {
      scrollToElement(invalidField, getTopOffset())
    } else if (errorNames.length > 0) {
      const firstErrorEl = document.querySelector(`[name="${errorNames[0]}"]`)
      if (firstErrorEl) {
        scrollToElement(firstErrorEl as HTMLElement, getTopOffset())
      }
    }

    return firstIncompleteRoomIndex !== -1
  }, [runPreSubmitCallbacks, rooms, methods.formState.errors, getTopOffset])

  const isRedemptionBooking = booking.searchType === SEARCH_TYPE_REDEMPTION
  const handleSubmit = useCallback(
    async (data: PaymentFormData) => {
      setIsSubmitting(true)

      const isRoomInvalid = await scrollToInvalidField()
      if (isRoomInvalid) {
        setIsSubmitting(false)
        return
      }

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

      const guarantee = data.guarantee
      const paymentMethod = getPaymentMethod({
        paymentMethod: data.paymentMethod,
        hasFlexRates,
        isRedemptionBooking,
        redemptionType,
      })
      const payment = getPaymentData({
        guarantee,
        bookingMustBeGuaranteed,
        hasOnlyFlexRates,
        paymentMethod,
        savedCreditCard,
        isRedemptionBooking,
        lang,
      })

      const paymentMethodType = savedCreditCard
        ? savedCreditCard.type
        : paymentMethod
      trackPaymentEvents({
        isSavedCreditCard: !!savedCreditCard,
        paymentMethodType,
        guarantee,
        smsEnable: data.smsConfirmation,
        bookingMustBeGuaranteed,
        hasOnlyFlexRates,
        hotelId,
      })

      const payload: CreateBookingInput = {
        checkInDate: fromDate,
        checkOutDate: toDate,
        hotelId,
        language: lang,
        payment: payment ?? undefined,
        rooms: rooms.map(
          ({ room }, idx): CreateBookingInput["rooms"][number] => {
            const isMainRoom = idx === 0
            let rateCode = ""
            if (isMainRoom && isLoggedIn) {
              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
            }

            const phoneNumber = formatPhoneNumber(
              room.guest.phoneNumber,
              room.guest.phoneNumberCC
            )

            const guest: CreateBookingInput["rooms"][number]["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,
              partnerLoyaltyNumber: null,
            }

            if (isMainRoom) {
              // Only valid for main room
              guest.partnerLoyaltyNumber =
                user?.data?.partnerLoyaltyNumber || null
              guest.dateOfBirth =
                "dateOfBirth" in room.guest && room.guest.dateOfBirth
                  ? room.guest.dateOfBirth
                  : undefined
              guest.postalCode =
                "zipCode" in room.guest && room.guest.zipCode
                  ? room.guest.zipCode
                  : undefined
            }

            const packages: CreateBookingInput["rooms"][number]["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,
            }

            return {
              adults: room.adults,
              bookingCode: room.roomRate.bookingCode,
              childrenAges: room.childrenInRoom?.map((child) => ({
                age: child.age,
                bedType: bedTypeMap[parseInt(child.bed.toString())],
              })),
              guest,
              packages,
              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)
    },
    [
      setIsSubmitting,
      scrollToInvalidField,
      savedCreditCards,
      hasFlexRates,
      redemptionType,
      bookingMustBeGuaranteed,
      hasOnlyFlexRates,
      lang,
      isRedemptionBooking,
      hotelId,
      fromDate,
      toDate,
      rooms,
      initiateBooking,
      isLoggedIn,
      booking.rooms,
      user?.data?.partnerLoyaltyNumber,
    ]
  )

  const handleInvalidSubmit = async () => {
    const valid = await methods.trigger()
    if (!valid) {
      await scrollToInvalidField()
    }
  }

  const { preHeading, heading, subHeading, showLearnMore } =
    getPaymentHeadingConfig(intl, bookingMustBeGuaranteed, hasOnlyFlexRates)

  return (
    <section
      className={cx(styles.paymentSection, {
        [styles.isSubmitting]: isSubmitting,
      })}
    >
      <header className={styles.header}>
        <div>
          {preHeading ? (
            <Typography variant="Title/Overline/sm">
              <p>{preHeading}</p>
            </Typography>
          ) : null}
          <Typography variant="Title/Subtitle/md">
            <h2>{heading}</h2>
          </Typography>
          {subHeading ? (
            <Typography variant="Body/Paragraph/mdBold">
              <p>{subHeading}</p>
            </Typography>
          ) : null}
        </div>
        {showLearnMore ? <GuaranteeInfo /> : null}
      </header>
      <BookingAlert isVisible={showBookingAlert} />
      <FormProvider {...methods}>
        <form
          className={styles.paymentForm}
          onSubmit={methods.handleSubmit(handleSubmit, handleInvalidSubmit)}
          id={formId}
        >
          <ConfirmBooking
            savedCreditCards={savedCreditCards}
            otherPaymentOptions={otherPaymentOptions}
            hasOnlyFlexRates={hasOnlyFlexRates}
            hasMixedRates={hasMixedRates}
            isRedemptionBooking={isRedemptionBooking}
            bookingMustBeGuaranteed={bookingMustBeGuaranteed}
          />

          <Button
            className={styles.submitButton}
            type="submit"
            isDisabled={isSubmitting}
            isPending={isSubmitting}
            size="md"
          >
            {intl.formatMessage({
              id: "enterDetails.completeBooking",
              defaultMessage: "Complete booking",
            })}
          </Button>
        </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>
  )
}

const scrollToElement = (el: HTMLElement, offset: number) => {
  const top = el.getBoundingClientRect().top + window.scrollY - offset - 20
  window.scrollTo({ top, behavior: "smooth" })
  const input = el.querySelector<HTMLElement>("input")
  input?.focus({ preventScroll: true })
}

function useBookingStatusRedirect({
  refId,
  enabled,
  onError,
}: {
  refId: string
  enabled: boolean
  onError: (errorMessage: string) => void
}) {
  const router = useRouter()
  const lang = useLang()
  const intl = useIntl()

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

  useEffect(() => {
    if (bookingStatus?.data?.booking.paymentUrl) {
      router.push(bookingStatus.data.booking.paymentUrl)
      return
    }

    if (
      bookingStatus?.data?.booking.reservationStatus ===
      BookingStatusEnum.BookingCompleted
    ) {
      const mainRoom = bookingStatus.data.booking.rooms[0]
      clearBookingWidgetState()
      // Cookie is used by Booking Confirmation page to validate that the user came from payment callback
      document.cookie = `bcsig=${bookingStatus.data.sig}; Path=/; Max-Age=60; Secure; SameSite=Strict`
      const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(mainRoom.refId)}`
      router.push(confirmationUrl)
      return
    }

    if (bookingStatus.isTimeout) {
      onError("Timeout")
    }
  }, [bookingStatus.data, bookingStatus.isTimeout, router, intl, lang, onError])
}

function isNotNull<T>(value: T | null): value is T {
  return value !== null
}

function trackPaymentEvents(data: {
  isSavedCreditCard: boolean
  paymentMethodType: string
  guarantee: boolean
  smsEnable: boolean
  bookingMustBeGuaranteed: boolean
  hasOnlyFlexRates: boolean
  hotelId: string
}) {
  const {
    isSavedCreditCard,
    paymentMethodType,
    guarantee,
    smsEnable,
    bookingMustBeGuaranteed,
    hasOnlyFlexRates,
    hotelId,
  } = data

  if (guarantee || (bookingMustBeGuaranteed && hasOnlyFlexRates)) {
    const lateArrivalGuarantee = guarantee ? "yes" : "mandatory"
    writeGlaToSessionStorage(
      lateArrivalGuarantee,
      hotelId,
      paymentMethodType,
      isSavedCreditCard
    )
    trackGlaSaveCardAttempt({
      hotelId,
      hasSavedCreditCard: isSavedCreditCard,
      creditCardType: isSavedCreditCard ? paymentMethodType : undefined,
      lateArrivalGuarantee,
    })
  } else if (!hasOnlyFlexRates) {
    trackPaymentEvent({
      event: "paymentAttemptStart",
      hotelId,
      method: paymentMethodType,
      isSavedCreditCard,
      smsEnable,
      status: "attempt",
    })
  }
  writePaymentInfoToSessionStorage(paymentMethodType, isSavedCreditCard)
}
