"use client"

import { zodResolver } from "@hookform/resolvers/zod"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useEffect, useState } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"

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

import { PaymentMethodEnum } from "@/constants/booking"
import { guaranteeCallback } from "@/constants/routes/hotelReservation"
import { env } from "@/env/client"
import { dt } from "@/lib/dt"
import { trpc } from "@/lib/trpc/client"
import {
  AncillaryStepEnum,
  type BreakfastData,
  useAddAncillaryStore,
} from "@/stores/my-stay/add-ancillary-flow"

import {
  buildAncillaryPackages,
  clearAncillarySessionData,
  generateDeliveryOptions,
  getAncillarySessionData,
  setAncillarySessionData,
} from "@/components/HotelReservation/MyStay/utils/ancillaries"
import LoadingSpinner from "@/components/LoadingSpinner"
import Modal from "@/components/Modal"
import Divider from "@/components/TempDesignSystem/Divider"
import { toast } from "@/components/TempDesignSystem/Toasts"
import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting"
import {
  trackAncillaryFailed,
  trackAncillarySuccess,
  trackGlaAncillaryAttempt,
} from "@/utils/tracking/myStay"

import { type AncillaryFormData, ancillaryFormSchema } from "../schema"
import ActionButtons from "./ActionButtons"
import PriceDetails from "./PriceDetails"
import Steps from "./Steps"

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

import type {
  AddAncillaryFlowModalProps,
  Packages,
} from "@/types/components/myPages/myStay/ancillaries"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"

export default function AddAncillaryFlowModal({
  booking,
  packages,
  user,
  savedCreditCards,
}: AddAncillaryFlowModalProps) {
  const {
    currentStep,
    selectedAncillary,
    closeModal,
    breakfastData,
    setBreakfastData,
    isBreakfast,
  } = useAddAncillaryStore((state) => ({
    currentStep: state.currentStep,
    selectedAncillary: state.selectedAncillary,
    closeModal: state.closeModal,
    breakfastData: state.breakfastData,
    setBreakfastData: state.setBreakfastData,
    isBreakfast: state.isBreakfast,
  }))
  const intl = useIntl()
  const lang = useLang()
  const router = useRouter()
  const searchParams = useSearchParams()
  const pathname = usePathname()

  const [isPriceDetailsOpen, setIsPriceDetailsOpen] = useState(false)
  const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang)}`
  const deliveryTimeOptions = generateDeliveryOptions()

  const defaultDeliveryTime = deliveryTimeOptions[0].value

  const hasInsufficientPoints =
    (user?.membership?.currentPoints ?? 0) < (selectedAncillary?.points ?? 0)

  const formMethods = useForm({
    defaultValues: {
      quantityWithPoints: null,
      quantityWithCard:
        !user || hasInsufficientPoints || isBreakfast ? 1 : null,
      deliveryTime: defaultDeliveryTime,
      optionalText: "",
      termsAndConditions: false,
      paymentMethod: booking.guaranteeInfo
        ? PaymentMethodEnum.card
        : savedCreditCards?.length
          ? savedCreditCards[0].id
          : PaymentMethodEnum.card,
    },
    mode: "onChange",
    reValidateMode: "onChange",
    resolver: zodResolver(ancillaryFormSchema),
  })

  const ancillaryErrorMessage = intl.formatMessage(
    {
      defaultMessage:
        "Something went wrong. {ancillary} could not be added to your booking!",
    },
    { ancillary: selectedAncillary?.title }
  )

  function togglePriceDetails() {
    setIsPriceDetailsOpen((isOpen) => !isOpen)
  }

  const utils = trpc.useUtils()
  const addAncillary = trpc.booking.packages.useMutation()

  const { guaranteeBooking, isLoading, handleGuaranteeError } =
    useGuaranteeBooking(booking.refId, true, booking.hotelId)

  function validateTermsAndConditions(data: AncillaryFormData): boolean {
    if (!data.termsAndConditions) {
      formMethods.setError("termsAndConditions", {
        message: "You must accept the terms",
      })
      return false
    }
    return true
  }

  function handleAncillarySubmission(
    data: AncillaryFormData,
    packages: {
      code: string
      quantity: number
      comment: string | undefined
    }[]
  ) {
    addAncillary.mutate(
      {
        refId: booking.refId,
        ancillaryComment: data.optionalText,
        ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
          ? data.deliveryTime
          : undefined,
        packages: packages,
        language: lang,
      },
      {
        onSuccess: (result) => {
          if (result) {
            trackAncillarySuccess(
              booking.confirmationNumber,
              packages,
              data.deliveryTime,
              "ancillary",
              selectedAncillary,
              breakfastData,
              booking.guaranteeInfo?.cardType,
              booking.roomTypeCode
            )
            toast.success(
              intl.formatMessage(
                {
                  defaultMessage: "{ancillary} added to your booking!",
                },
                { ancillary: selectedAncillary?.title }
              )
            )
            clearAncillarySessionData()
            closeModal()
            utils.booking.get.invalidate({
              refId: booking.refId,
            })
            router.refresh()
          } else {
            trackAncillaryFailed(
              packages,
              data.deliveryTime,
              selectedAncillary,
              breakfastData
            )
            toast.error(ancillaryErrorMessage)
          }
        },
        onError: () => {
          trackAncillaryFailed(
            packages,
            data.deliveryTime,
            selectedAncillary,
            breakfastData
          )
          toast.error(ancillaryErrorMessage)
        },
      }
    )
  }

  function handleGuaranteePayment(data: AncillaryFormData, packages: any) {
    const savedCreditCard = savedCreditCards?.find(
      (card) => card.id === data.paymentMethod
    )
    trackGlaAncillaryAttempt(
      savedCreditCard,
      packages,
      selectedAncillary,
      data.deliveryTime,
      breakfastData
    )
    if (booking.refId) {
      const card = savedCreditCard
        ? {
            alias: savedCreditCard.alias,
            expiryDate: savedCreditCard.expirationDate,
            cardType: savedCreditCard.cardType,
          }
        : undefined
      guaranteeBooking.mutate({
        refId: booking.refId,
        language: lang,
        ...(card && { card }),
        success: `${guaranteeRedirectUrl}?status=success&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
        error: `${guaranteeRedirectUrl}?status=error&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
        cancel: `${guaranteeRedirectUrl}?status=cancel&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
      })
    } else {
      handleGuaranteeError("No confirmation number")
    }
  }

  function buildBreakfastPackages(
    data: AncillaryFormData,
    breakfastData: BreakfastData
  ) {
    const packages = [
      {
        code: BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
        quantity: breakfastData.nrOfAdults,
        comment: data.optionalText || undefined,
      },
      {
        code: BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
        quantity: breakfastData.nrOfPayingChildren,
        comment: data.optionalText || undefined,
      },
      {
        code: BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
        quantity: breakfastData.nrOfFreeChildren,
        comment: data.optionalText || undefined,
      },
    ]

    return packages.filter((pkg) => pkg.quantity > 0)
  }

  const onSubmit = (data: AncillaryFormData) => {
    if (!validateTermsAndConditions(data)) return

    const packagesToAdd = !isBreakfast
      ? buildAncillaryPackages(data, selectedAncillary)
      : breakfastData
        ? buildBreakfastPackages(data, breakfastData)
        : []

    if (isBreakfast && !breakfastData) {
      toast.error(
        intl.formatMessage({
          defaultMessage: "Something went wrong!",
        })
      )
      return
    }
    setAncillarySessionData({
      formData: data,
      selectedAncillary,
      packages: packagesToAdd,
      isBreakfast,
      breakfastData,
    })
    const shouldSkipGuarantee =
      booking.guaranteeInfo || (data.quantityWithCard ?? 0) <= 0

    if (shouldSkipGuarantee) {
      handleAncillarySubmission(data, packagesToAdd)
    } else {
      handleGuaranteePayment(data, packagesToAdd)
    }
  }

  useEffect(() => {
    const errorCode = searchParams.get("errorCode")
    const ancillary = searchParams.get("ancillary")
    if ((errorCode && ancillary) || errorCode === "AncillaryFailed") {
      const queryParams = new URLSearchParams(searchParams.toString())
      if (ancillary) {
        queryParams.delete("ancillary")
      }
      queryParams.delete("errorCode")
      const savedData = getAncillarySessionData()
      if (savedData?.formData) {
        const updatedFormData = {
          ...savedData.formData,
          paymentMethod: booking?.guaranteeInfo
            ? PaymentMethodEnum.card
            : savedData.formData.paymentMethod,
        }
        formMethods.reset(updatedFormData)
      }
      router.replace(`${pathname}?${queryParams.toString()}`)
    }
  }, [searchParams, pathname, formMethods, router, booking.guaranteeInfo])

  useEffect(() => {
    setBreakfastData(
      calculateBreakfastData(
        isBreakfast,
        packages,
        booking.adults,
        booking.childrenAges,
        dt(booking.checkOutDate)
          .startOf("day")
          .diff(dt(booking.checkInDate).startOf("day"), "days")
      )
    )
  }, [
    booking.adults,
    booking.checkInDate,
    booking.checkOutDate,
    booking.childrenAges,
    isBreakfast,
    packages,
    setBreakfastData,
  ])

  if (isLoading) {
    return (
      <div>
        <LoadingSpinner />
      </div>
    )
  }

  const modalTitle =
    currentStep === AncillaryStepEnum.selectAncillary
      ? intl.formatMessage({
          defaultMessage: "Upgrade your stay",
        })
      : selectedAncillary?.title
  return (
    <Modal isOpen={true} onToggle={closeModal} title={modalTitle}>
      <div
        className={`${styles.modalWrapper} ${currentStep === AncillaryStepEnum.selectAncillary ? styles.selectAncillarycontainer : ""}`}
      >
        <FormProvider {...formMethods}>
          <form
            onSubmit={formMethods.handleSubmit(onSubmit)}
            className={styles.form}
            id="add-ancillary-form-id"
          >
            <div className={styles.modalScrollable}>
              {selectedAncillary && (
                <>
                  {currentStep !== AncillaryStepEnum.confirmation && (
                    <div className={styles.contentContainer}>
                      <div className={styles.price}>
                        <Typography variant="Body/Paragraph/mdBold">
                          {isBreakfast ? (
                            <BreakfastPriceList />
                          ) : (
                            <p>
                              {formatPrice(
                                intl,
                                selectedAncillary.price.total,
                                selectedAncillary.price.currency
                              )}
                            </p>
                          )}
                        </Typography>
                        {selectedAncillary.points && (
                          <div className={styles.pointsDivider}>
                            <Divider variant="vertical" color="subtle" />
                            <Typography variant="Body/Paragraph/mdBold">
                              <p>
                                {intl.formatMessage(
                                  {
                                    defaultMessage: "{value} points",
                                  },
                                  {
                                    value: selectedAncillary.points,
                                  }
                                )}
                              </p>
                            </Typography>
                          </div>
                        )}
                      </div>
                      <div className={styles.description}>
                        {selectedAncillary.description && (
                          <Typography variant="Body/Paragraph/mdRegular">
                            <p
                              dangerouslySetInnerHTML={{
                                __html: selectedAncillary.description,
                              }}
                            ></p>
                          </Typography>
                        )}
                      </div>
                    </div>
                  )}
                </>
              )}
              <Steps user={user} savedCreditCards={savedCreditCards} />
              {currentStep === AncillaryStepEnum.selectAncillary ? null : (
                <div
                  className={
                    currentStep === AncillaryStepEnum.confirmation ||
                    isBreakfast
                      ? styles.confirmStep
                      : ""
                  }
                >
                  <PriceDetails isPriceDetailsOpen={isPriceDetailsOpen} />
                  <ActionButtons
                    isPriceDetailsOpen={isPriceDetailsOpen}
                    togglePriceDetails={togglePriceDetails}
                    isSubmitting={addAncillary.isPending || isLoading}
                  />
                </div>
              )}
            </div>
          </form>
        </FormProvider>
      </div>
    </Modal>
  )
}

function BreakfastPriceList() {
  const intl = useIntl()
  const breakfastData = useAddAncillaryStore((state) => state.breakfastData)

  if (!breakfastData) {
    return intl.formatMessage({
      defaultMessage: "Unable to display breakfast prices.",
    })
  }

  return (
    <div>
      <div className={styles.breakfastPriceList}>
        <Typography variant="Body/Paragraph/mdBold">
          <span>
            {intl.formatMessage(
              {
                defaultMessage: "{price}/night per adult",
              },
              {
                price: `${breakfastData.priceAdult} ${breakfastData.currency}`,
              }
            )}
          </span>
        </Typography>

        {breakfastData.nrOfPayingChildren > 0 && (
          <>
            <div className={styles.divider}>
              <Divider variant="vertical" color="baseSurfaceSubtleNormal" />
            </div>

            <Typography variant="Body/Paragraph/mdBold">
              <span>
                {intl.formatMessage(
                  {
                    defaultMessage: "{price}/night for kids (ages 4–12)",
                  },
                  {
                    price: `${breakfastData.priceChild} ${breakfastData.currency}`,
                  }
                )}
              </span>
            </Typography>
          </>
        )}

        {breakfastData.nrOfFreeChildren > 0 && (
          <>
            <div className={styles.divider}>
              <Divider variant="vertical" color="baseSurfaceSubtleNormal" />
            </div>

            <Typography variant="Body/Paragraph/mdBold">
              <span>
                {intl.formatMessage({
                  defaultMessage: "Free for kids (under 4)",
                })}
              </span>
            </Typography>
          </>
        )}
      </div>
    </div>
  )
}

/**
 * This function calculates some breakfast data in the store.
 * It is used in various places in the add flow, but only needs
 * to be calculated once.
 */
function calculateBreakfastData(
  isBreakfast: boolean,
  packages: Packages | null,
  nrOfAdults: number,
  childrenAges: number[],
  nrOfNights: number
): BreakfastData | null {
  if (!isBreakfast) {
    return null
  }

  const { nrOfPayingChildren, nrOfFreeChildren } = childrenAges.reduce(
    (total, childAge) => {
      if (childAge >= 4) {
        total.nrOfPayingChildren = total.nrOfPayingChildren + 1
      } else {
        total.nrOfFreeChildren = total.nrOfFreeChildren + 1
      }
      return total
    },
    { nrOfPayingChildren: 0, nrOfFreeChildren: 0 }
  )

  const adultPackage = packages?.find(
    (p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
  )
  const childPackage = packages?.find(
    (p) => p.code === BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST
  )
  const priceAdult = adultPackage?.localPrice.price
  const priceChild = childPackage?.localPrice.price
  const currency =
    adultPackage?.localPrice.currency ?? childPackage?.localPrice.currency

  if (
    typeof priceAdult !== "number" ||
    typeof priceChild !== "number" ||
    typeof currency !== "string"
  ) {
    return null
  } else {
    return {
      nrOfAdults,
      nrOfPayingChildren,
      nrOfFreeChildren,
      nrOfNights,
      priceAdult,
      priceChild,
      currency,
    }
  }
}
