import deepmerge from "deepmerge"
import { produce } from "immer"
import { useContext } from "react"
import { create, useStore } from "zustand"

import { REDEMPTION } from "@/constants/booking"
import { getDefaultCountryFromLang } from "@/constants/languages"
import { dt } from "@/lib/dt"

import {
  sumPackages,
  sumPackagesRequestedPrice,
} from "@/components/HotelReservation/utils"
import { DetailsContext } from "@/contexts/Details"

import {
  add,
  calcTotalPrice,
  calculateCorporateChequePrice,
  calculateVoucherPrice,
  checkRoomProgress,
  extractGuestFromUser,
  getRoomPrice,
  getTotalPrice,
  writeToSessionStorage,
} from "./helpers"

import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
import type { Price } from "@/types/components/hotelReservation/price"
import { CurrencyEnum } from "@/types/enums/currency"
import { StepEnum } from "@/types/enums/step"
import type {
  DetailsState,
  InitialState,
  RoomState,
} from "@/types/stores/enter-details"
import type { SafeUser } from "@/types/user"
import type { Lang } from "@/constants/routes/hotelReservation"

const defaultGuestState = {
  countryCode: "",
  dateOfBirth: "",
  email: "",
  firstName: "",
  join: false,
  lastName: "",
  membershipNo: "",
  phoneNumber: "",
  phoneNumberCC: "",
  zipCode: "",
}

export const detailsStorageName = "rooms-details-storage"

export function createDetailsStore(
  initialState: InitialState,
  searchParams: string,
  user: SafeUser,
  breakfastPackages: BreakfastPackages,
  lang: Lang
) {
  const isMember = !!user
  const isRedemption =
    new URLSearchParams(searchParams).get("searchtype") === REDEMPTION

  const isVoucher = initialState.rooms.some(
    (room) => "voucher" in room.roomRate
  )
  const isCorpChq = initialState.rooms.some(
    (room) => "corporateCheque" in room.roomRate
  )

  let initialTotalPrice: Price
  const roomOneRoomRate = initialState.rooms[0].roomRate
  const initialRoomRates = initialState.rooms.map((r) => r.roomRate)
  if (isRedemption && "redemption" in roomOneRoomRate) {
    initialTotalPrice = {
      local: {
        currency: CurrencyEnum.POINTS,
        price: roomOneRoomRate.redemption.localPrice.pointsPerStay,
      },
    }
    if (roomOneRoomRate.redemption.localPrice.currency) {
      initialTotalPrice.local.additionalPriceCurrency =
        roomOneRoomRate.redemption.localPrice.currency
    }
    if (roomOneRoomRate.redemption.localPrice.additionalPricePerStay) {
      initialTotalPrice.local.additionalPrice =
        roomOneRoomRate.redemption.localPrice.additionalPricePerStay
    }
  } else if (isVoucher) {
    const pkgs = initialState.rooms.flatMap((room) => room.roomFeatures || [])
    initialTotalPrice = calculateVoucherPrice(initialRoomRates, pkgs)
  } else if (isCorpChq) {
    initialTotalPrice = calculateCorporateChequePrice(initialRoomRates)
  } else {
    initialTotalPrice = getTotalPrice(initialRoomRates, isMember)
  }

  initialState.rooms.forEach((room) => {
    if (room.roomFeatures) {
      const pkgsSum = sumPackages(room.roomFeatures)
      const pkgsSumRequested = sumPackagesRequestedPrice(room.roomFeatures)

      if ("corporateCheque" in room.roomRate || "redemption" in room.roomRate) {
        initialTotalPrice.local.additionalPrice = add(
          initialTotalPrice.local.additionalPrice,
          pkgsSum.price
        )
        if (
          !initialTotalPrice.local.additionalPriceCurrency &&
          pkgsSum.currency
        ) {
          initialTotalPrice.local.additionalPriceCurrency = pkgsSum.currency
        }

        if (initialTotalPrice.requested) {
          initialTotalPrice.requested.additionalPrice = add(
            initialTotalPrice.requested.additionalPrice,
            pkgsSumRequested.price
          )
          if (
            !initialTotalPrice.requested.additionalPriceCurrency &&
            pkgsSumRequested.currency
          ) {
            initialTotalPrice.requested.additionalPriceCurrency =
              pkgsSumRequested.currency
          }
        }
      } else if ("public" in room.roomRate) {
        if (initialTotalPrice.requested) {
          initialTotalPrice.requested.price = add(
            initialTotalPrice.requested.price,
            pkgsSumRequested.price
          )
        }

        initialTotalPrice.local.price = add(
          initialTotalPrice.local.price,
          pkgsSum.price
        )
      }
    }
  })

  const availableBeds = initialState.rooms.reduce<
    DetailsState["availableBeds"]
  >((total, room) => {
    for (const bed of room.bedTypes) {
      if (!total[bed.value]) {
        total[bed.value] = bed.roomsLeft
      }
    }
    return total
  }, {})

  return create<DetailsState>()((set) => ({
    availableBeds,
    booking: initialState.booking,
    breakfastPackages,
    canProceedToPayment: false,
    isSubmitting: false,
    isSummaryOpen: false,
    lastRoom: initialState.booking.rooms.length - 1,
    rooms: initialState.rooms.map((room, idx) => {
      const steps: RoomState["steps"] = {
        [StepEnum.selectBed]: {
          step: StepEnum.selectBed,
          isValid: !!room.bedType,
        },
        [StepEnum.breakfast]: {
          step: StepEnum.breakfast,
          isValid: false,
        },
        [StepEnum.details]: {
          step: StepEnum.details,
          isValid: isMember && idx === 0,
        },
      }

      if (room.breakfastIncluded || !breakfastPackages.length) {
        delete steps[StepEnum.breakfast]
      }

      return {
        actions: {
          setIncomplete() {
            return set(
              produce((state: DetailsState) => {
                state.rooms[idx].isComplete = false
              })
            )
          },
          updateBedType(bedType) {
            return set(
              produce((state: DetailsState) => {
                const currentlySelectedBed =
                  state.rooms[idx].room.bedType?.roomTypeCode
                if (currentlySelectedBed) {
                  state.availableBeds[currentlySelectedBed] =
                    state.availableBeds[currentlySelectedBed] + 1
                }
                state.availableBeds[bedType.roomTypeCode] =
                  state.availableBeds[bedType.roomTypeCode] - 1

                state.rooms[idx].steps[StepEnum.selectBed].isValid = true
                state.rooms[idx].room.bedType = bedType

                const isAllStepsCompleted = checkRoomProgress(
                  state.rooms[idx].steps
                )
                if (isAllStepsCompleted) {
                  state.rooms[idx].isComplete = true
                }

                writeToSessionStorage({
                  booking: state.booking,
                  rooms: state.rooms,
                })
              })
            )
          },
          updateBreakfast(breakfast) {
            return set(
              produce((state: DetailsState) => {
                const currentRoom = state.rooms[idx]
                if (currentRoom.steps[StepEnum.breakfast]) {
                  currentRoom.steps[StepEnum.breakfast].isValid = true
                }

                currentRoom.room.breakfast = breakfast

                const nights = dt(state.booking.toDate).diff(
                  state.booking.fromDate,
                  "days"
                )

                state.totalPrice = calcTotalPrice(
                  state.rooms,
                  currentRoom.room.roomPrice.perStay.local.currency,
                  isMember,
                  nights
                )

                const isAllStepsCompleted = checkRoomProgress(
                  state.rooms[idx].steps
                )
                if (isAllStepsCompleted) {
                  state.rooms[idx].isComplete = true
                }

                writeToSessionStorage({
                  booking: state.booking,
                  rooms: state.rooms,
                })
              })
            )
          },
          updatePriceForMembershipNo(membershipNo, isValid) {
            return set(
              produce((state: DetailsState) => {
                const currentRoom = state.rooms[idx].room

                currentRoom.guest.join = false
                currentRoom.guest.membershipNo = isValid ? membershipNo : ""

                const isValidMembershipNo = isValid && !!membershipNo
                currentRoom.roomPrice = getRoomPrice(
                  currentRoom.roomRate,
                  isValidMembershipNo
                )

                const nights = dt(state.booking.toDate).diff(
                  state.booking.fromDate,
                  "days"
                )

                state.totalPrice = calcTotalPrice(
                  state.rooms,
                  state.totalPrice.local.currency,
                  isMember,
                  nights
                )

                writeToSessionStorage({
                  booking: state.booking,
                  rooms: state.rooms,
                })
              })
            )
          },
          updateJoin(join) {
            return set(
              produce((state: DetailsState) => {
                const currentRoom = state.rooms[idx].room

                currentRoom.guest.join = join

                if (join) {
                  currentRoom.guest.membershipNo = ""
                }

                currentRoom.roomPrice = getRoomPrice(currentRoom.roomRate, join)

                const nights = dt(state.booking.toDate).diff(
                  state.booking.fromDate,
                  "days"
                )

                state.totalPrice = calcTotalPrice(
                  state.rooms,
                  state.totalPrice.local.currency,
                  isMember,
                  nights
                )

                writeToSessionStorage({
                  booking: state.booking,
                  rooms: state.rooms,
                })
              })
            )
          },
          updateDetails(data) {
            return set(
              produce((state: DetailsState) => {
                state.rooms[idx].steps[StepEnum.details].isValid = true
                const currentRoom = state.rooms[idx].room

                currentRoom.guest.countryCode = data.countryCode
                currentRoom.guest.email = data.email
                currentRoom.guest.firstName = data.firstName
                currentRoom.guest.join = data.join
                currentRoom.guest.lastName = data.lastName
                currentRoom.guest.phoneNumber = data.phoneNumber
                currentRoom.guest.phoneNumberCC = data.phoneNumberCC

                if (data.specialRequest?.comment) {
                  currentRoom.specialRequest.comment =
                    data.specialRequest.comment
                }

                if (data.join) {
                  currentRoom.guest.membershipNo = undefined
                } else {
                  currentRoom.guest.membershipNo = data.membershipNo
                }

                // Only valid for room 1
                if (idx === 0 && data.join && !isMember) {
                  if ("dateOfBirth" in currentRoom.guest) {
                    currentRoom.guest.dateOfBirth = data.dateOfBirth
                  }
                  if ("zipCode" in currentRoom.guest) {
                    currentRoom.guest.zipCode = data.zipCode
                  }
                }

                const isMemberAndRoomOne = idx === 0 && isMember

                currentRoom.roomPrice = getRoomPrice(
                  currentRoom.roomRate,
                  Boolean(data.join || data.membershipNo || isMemberAndRoomOne)
                )

                const nights = dt(state.booking.toDate).diff(
                  state.booking.fromDate,
                  "days"
                )

                state.totalPrice = calcTotalPrice(
                  state.rooms,
                  state.totalPrice.local.currency,
                  isMember,
                  nights
                )

                const isAllStepsCompleted = checkRoomProgress(
                  state.rooms[idx].steps
                )
                if (isAllStepsCompleted) {
                  state.rooms[idx].isComplete = true
                }

                writeToSessionStorage({
                  booking: state.booking,
                  rooms: state.rooms,
                })
              })
            )
          },
        },
        room: {
          ...room,
          adults: initialState.booking.rooms[idx].adults,
          childrenInRoom: initialState.booking.rooms[idx].childrenInRoom,
          bedType: room.bedType,
          breakfast:
            !breakfastPackages.length || room.breakfastIncluded
              ? false
              : undefined,
          guest:
            isMember && idx === 0
              ? deepmerge(defaultGuestState, extractGuestFromUser(user))
              : {
                  ...defaultGuestState,
                  phoneNumberCC: getDefaultCountryFromLang(lang),
                },
          roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
          specialRequest: {
            comment: "",
          },
        },
        isComplete: false,
        steps,
      }
    }),
    searchParamString: searchParams,
    totalPrice: initialTotalPrice,
    vat: initialState.vat,
    defaultCurrency: breakfastPackages[0].localPrice.currency,
    preSubmitCallbacks: {},

    actions: {
      setIsSubmitting(isSubmitting) {
        return set(
          produce((state: DetailsState) => {
            state.isSubmitting = isSubmitting
          })
        )
      },
      setTotalPrice(totalPrice) {
        return set(
          produce((state: DetailsState) => {
            state.totalPrice.requested = totalPrice.requested
            state.totalPrice.local = totalPrice.local
          })
        )
      },
      toggleSummaryOpen() {
        return set(
          produce((state: DetailsState) => {
            state.isSummaryOpen = !state.isSummaryOpen
          })
        )
      },
      updateSeachParamString(searchParamString) {
        return set(
          produce((state: DetailsState) => {
            state.searchParamString = searchParamString
          })
        )
      },
      addPreSubmitCallback(name, callback) {
        return set(
          produce((state: DetailsState) => {
            state.preSubmitCallbacks[name] = callback
          })
        )
      },
    },
  }))
}

export function useEnterDetailsStore<T>(selector: (store: DetailsState) => T) {
  const store = useContext(DetailsContext)

  if (!store) {
    throw new Error("useEnterDetailsStore must be used within DetailsProvider")
  }

  return useStore(store, selector)
}
