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

import { REDEMPTION } from "@/constants/booking"

import { RatesContext } from "@/contexts/Rates"

import {
  clearRoomSelectionFromUrl,
  findDefaultCurrency,
  findProductInRoom,
  findSelectedRate,
} from "./helpers"

import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
import type { Package, Packages } from "@/types/requests/packages"
import type { InitialState, RatesState } from "@/types/stores/rates"
import type { PriceProduct } from "@/types/trpc/routers/hotel/roomAvailability"

export function createRatesStore({
  booking,
  hotelType,
  labels,
  pathname,
  roomCategories,
  roomsAvailability,
  searchParams,
  vat,
}: InitialState) {
  const packageOptions = [
    {
      code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
      description: labels.accessibilityRoom,
    },
    {
      code: RoomPackageCodeEnum.ALLERGY_ROOM,
      description: labels.allergyRoom,
    },
    {
      code: RoomPackageCodeEnum.PET_ROOM,
      description: labels.petRoom,
    },
  ]

  const roomsPackages: NonNullable<Packages>[] = []
  const roomConfigurations: RatesState["roomConfigurations"] = []
  if (roomsAvailability) {
    for (const availability of roomsAvailability) {
      if ("error" in availability) {
        // Availability request failed, default to empty array
        roomConfigurations.push([])
        roomsPackages.push([])
      } else {
        roomConfigurations.push(availability.roomConfigurations)
        roomsPackages.push(availability.packages)
      }
    }
  }

  const rateSummary: RatesState["rateSummary"] = []
  for (const [idx, room] of booking.rooms.entries()) {
    if (room.rateCode && room.roomTypeCode) {
      const roomConfiguration = roomConfigurations?.[idx]

      const selectedRoom = findSelectedRate(
        room.rateCode,
        room.counterRateCode,
        room.roomTypeCode,
        roomConfiguration
      )

      if (!selectedRoom) {
        const updatedSearchParams = clearRoomSelectionFromUrl(idx, searchParams)
        searchParams = updatedSearchParams
        window.history.replaceState(
          {},
          "",
          `${pathname}?${updatedSearchParams}`
        )
        continue
      }

      const product = findProductInRoom(
        room.rateCode,
        selectedRoom,
        room.counterRateCode
      )
      if (product) {
        const roomPackages = roomsPackages[idx].filter((pkg) =>
          room.packages?.includes(pkg.code)
        )

        rateSummary[idx] = {
          features: selectedRoom.features,
          product,
          packages: roomPackages,
          rate: product.rate,
          roomType: selectedRoom.roomType,
          roomTypeCode: selectedRoom.roomTypeCode,
        }
      } else {
        rateSummary[idx] = null
      }
    }
  }

  let activeRoom = rateSummary.length
  if (searchParams.has("modifyRateIndex")) {
    activeRoom = Number(searchParams.get("modifyRateIndex"))
  } else if (rateSummary.length === booking.rooms.length) {
    // Finds the first unselected room and sets that to active
    // if no unselected rooms it will return -1 and close all rooms
    const unselectedRoomIndex = rateSummary.findIndex((rate) => !rate)
    activeRoom = unselectedRoomIndex
  }

  const isRedemptionBooking = booking.searchType === REDEMPTION

  const defaultCurrency = findDefaultCurrency(roomsAvailability)

  return create<RatesState>()((set) => {
    return {
      activeRoom,
      booking,
      packageOptions,
      hotelType,
      isRedemptionBooking,
      pathname,
      rateSummary,
      roomConfigurations,
      roomCategories,
      roomsPackages,
      roomsAvailability,
      searchParams,
      vat,
      defaultCurrency,
      rooms: booking.rooms.map((room, idx) => {
        const roomConfiguration = roomConfigurations[idx]
        const roomPackages = roomsPackages[idx]
        const selectedPackages =
          room.packages
            ?.map((code) => roomPackages.find((pkg) => pkg.code === code))
            .filter((pkg): pkg is Package => Boolean(pkg)) ?? []

        const selectedRate =
          findSelectedRate(
            room.rateCode,
            room.counterRateCode,
            room.roomTypeCode,
            roomConfiguration
          ) ?? null

        let product = null
        if (selectedRate) {
          product = findProductInRoom(
            room.rateCode,
            selectedRate,
            room.counterRateCode
          )
        }
        const bookingCode = room.rateCode
          ? room.bookingCode
          : booking.bookingCode
        const selectedFilter =
          bookingCode && !isRedemptionBooking
            ? BookingCodeFilterEnum.Discounted
            : BookingCodeFilterEnum.All

        return {
          actions: {
            appendRegularRates(roomConfigurations) {
              return set(
                produce((state: RatesState) => {
                  state.rooms[idx].isFetchingAdditionalRate = false
                  if (roomConfigurations) {
                    const rooms = state.rooms[idx].rooms
                    const updatedRooms = rooms.map((currentRoom) => {
                      const incomingRoom = roomConfigurations.find(
                        (room) =>
                          room.roomType === currentRoom.roomType &&
                          room.roomTypeCode === currentRoom.roomTypeCode
                      )

                      if (incomingRoom) {
                        let campaign = currentRoom.campaign
                        if (incomingRoom.campaign.length) {
                          const newCampaign = [
                            ...campaign,
                            ...incomingRoom.campaign,
                          ].reduce((cpns, cpn) => {
                            if (cpns.has(cpn.rateDefinition.rateCode)) {
                              return cpns
                            }
                            cpns.set(cpn.rateDefinition.rateCode, cpn)
                            return cpns
                          }, new Map<string, PriceProduct>())
                          campaign = Array.from(newCampaign.values())
                        }

                        const currentRoomAvailable =
                          currentRoom.status === AvailabilityEnum.Available
                        const incomingRoomAvailable =
                          incomingRoom.status === AvailabilityEnum.Available
                        let status = AvailabilityEnum.NotAvailable
                        if (currentRoomAvailable || incomingRoomAvailable) {
                          status = AvailabilityEnum.Available
                        }

                        return {
                          ...currentRoom,
                          campaign,
                          products: [
                            ...currentRoom.products,
                            ...incomingRoom.products,
                          ],
                          regular: incomingRoom.regular,
                          status,
                        }
                      }

                      return currentRoom
                    })

                    state.rooms[idx].rooms = updatedRooms
                  } else {
                    state.rooms[idx].rooms = []
                  }
                })
              )
            },
            closeSection() {
              return set(
                produce((state: RatesState) => {
                  if (state.rateSummary.length === state.booking.rooms.length) {
                    state.activeRoom = -1
                  } else {
                    state.activeRoom = idx + 1
                  }
                })
              )
            },
            modifyRate() {
              return set(
                produce((state: RatesState) => {
                  state.activeRoom = idx
                })
              )
            },
            removeSelectedPackage(code) {
              return set(
                produce((state: RatesState) => {
                  state.rooms[idx].isFetchingPackages = true
                  const filteredSelectedPackages = state.rooms[
                    idx
                  ].selectedPackages.filter((c) => c.code !== code)
                  state.rooms[idx].selectedPackages = filteredSelectedPackages

                  if (
                    state.rooms[idx].selectedRate?.product.bookingCode ||
                    state.booking.bookingCode
                  ) {
                    state.rooms[idx].selectedFilter =
                      BookingCodeFilterEnum.Discounted
                  }

                  const searchParams = state.searchParams
                  if (filteredSelectedPackages.length) {
                    searchParams.set(
                      `room[${idx}].packages`,
                      filteredSelectedPackages.map((pkg) => pkg.code).join(",")
                    )
                  } else {
                    searchParams.delete(`room[${idx}].packages`)
                  }

                  state.searchParams = searchParams

                  window.history.replaceState(
                    {},
                    "",
                    `${state.pathname}?${searchParams}`
                  )
                })
              )
            },
            removeSelectedPackages() {
              return set(
                produce((state: RatesState) => {
                  state.rooms[idx].isFetchingPackages = true
                  state.rooms[idx].selectedPackages = []

                  if (
                    state.rooms[idx].selectedRate?.product.bookingCode ||
                    state.booking.bookingCode
                  ) {
                    state.rooms[idx].selectedFilter =
                      BookingCodeFilterEnum.Discounted
                  }

                  const searchParams = state.searchParams
                  searchParams.delete(`room[${idx}].packages`)

                  state.searchParams = searchParams

                  window.history.replaceState(
                    {},
                    "",
                    `${state.pathname}?${searchParams}`
                  )
                })
              )
            },
            removeSelectedRoom() {
              return set(
                produce((state: RatesState) => {
                  state.rateSummary[idx] = null

                  const searchParams = state.searchParams
                  searchParams.delete(`room[${idx}].counterratecode`)
                  searchParams.delete(`room[${idx}].ratecode`)
                  searchParams.delete(`room[${idx}].roomtype`)

                  state.searchParams = searchParams

                  window.history.replaceState(
                    {},
                    "",
                    `${state.pathname}?${searchParams}`
                  )
                })
              )
            },
            selectFilter(filter) {
              return set(
                produce((state: RatesState) => {
                  state.rooms[idx].selectedFilter = filter
                  state.rooms[idx].isFetchingAdditionalRate =
                    filter === BookingCodeFilterEnum.All
                })
              )
            },
            selectRate(selectedRate, isUserLoggedIn) {
              return set(
                produce((state: RatesState) => {
                  if (!selectedRate.product) {
                    return
                  }

                  state.rooms[idx].selectedRate = selectedRate
                  state.rateSummary[idx] = {
                    features: selectedRate.features,
                    packages: state.rooms[idx].selectedPackages,
                    product: selectedRate.product,
                    rate: selectedRate.product.rate,
                    roomType: selectedRate.roomType,
                    roomTypeCode: selectedRate.roomTypeCode,
                  }

                  const roomNr = idx + 1
                  const isMainRoom = roomNr === 1

                  let productRateCode = ""
                  if ("corporateCheque" in selectedRate.product) {
                    productRateCode =
                      selectedRate.product.corporateCheque.rateCode
                  }

                  if ("redemption" in selectedRate.product) {
                    productRateCode = selectedRate.product.redemption.rateCode
                  }

                  if ("voucher" in selectedRate.product) {
                    productRateCode = selectedRate.product.voucher.rateCode
                  }

                  if (
                    "public" in selectedRate.product &&
                    selectedRate.product.public
                  ) {
                    productRateCode = selectedRate.product.public.rateCode
                  }

                  let hasMemberRate = false
                  let memberRateCode = ""
                  if (
                    "member" in selectedRate.product &&
                    selectedRate.product.member
                  ) {
                    hasMemberRate = true
                    memberRateCode = selectedRate.product.member.rateCode
                  }

                  const isMemberRate =
                    isUserLoggedIn && isMainRoom && hasMemberRate

                  state.rooms[idx].bookingRoom.rateCode = isMemberRate
                    ? memberRateCode
                    : productRateCode
                  if (!isMemberRate && hasMemberRate) {
                    state.rooms[idx].bookingRoom.counterRateCode =
                      memberRateCode
                  }
                  state.rooms[idx].bookingRoom.roomTypeCode =
                    selectedRate.roomTypeCode
                  state.rooms[idx].bookingRoom.bookingCode =
                    selectedRate.product.bookingCode

                  const searchParams = new URLSearchParams(state.searchParams)
                  const counterratecode = isMemberRate
                    ? productRateCode
                    : memberRateCode
                  if (counterratecode) {
                    searchParams.set(
                      `room[${idx}].counterratecode`,
                      counterratecode
                    )
                  } else {
                    if (searchParams.has(`room[${idx}].counterratecode`)) {
                      searchParams.delete(`room[${idx}].counterratecode`)
                    }
                  }

                  const rateCode = isMemberRate
                    ? memberRateCode
                    : productRateCode
                  if (rateCode) {
                    searchParams.set(`room[${idx}].ratecode`, rateCode)
                  }

                  if (selectedRate.product.bookingCode) {
                    searchParams.set(
                      `room[${idx}].bookingCode`,
                      selectedRate.product.bookingCode
                    )
                  } else {
                    if (searchParams.has(`room[${idx}].bookingCode`)) {
                      searchParams.delete(`room[${idx}].bookingCode`)
                    }
                  }

                  searchParams.set(
                    `room[${idx}].roomtype`,
                    selectedRate.roomTypeCode
                  )

                  if (state.rateSummary.length === state.booking.rooms.length) {
                    state.activeRoom = -1
                  } else {
                    state.activeRoom = idx + 1
                  }

                  state.searchParams = searchParams

                  window.history.replaceState(
                    {},
                    "",
                    `${state.pathname}?${searchParams}`
                  )
                })
              )
            },
            selectPackages(selectedPackages) {
              return set(
                produce((state: RatesState) => {
                  state.rooms[idx].isFetchingPackages = true
                  const pkgs = state.roomsPackages[idx].filter((pkg) =>
                    selectedPackages.includes(pkg.code)
                  )
                  state.rooms[idx].selectedPackages = pkgs

                  if (
                    state.rooms[idx].selectedRate?.product.bookingCode ||
                    state.booking.bookingCode
                  ) {
                    state.rooms[idx].selectedFilter =
                      BookingCodeFilterEnum.Discounted
                  }

                  const searchParams = state.searchParams
                  if (selectedPackages.length) {
                    searchParams.set(
                      `room[${idx}].packages`,
                      selectedPackages.join(",")
                    )
                  } else {
                    searchParams.delete(`room[${idx}].packages`)
                  }

                  state.searchParams = searchParams

                  window.history.replaceState(
                    {},
                    "",
                    `${state.pathname}?${searchParams}`
                  )
                })
              )
            },
            updateRooms(rooms) {
              return set(
                produce((state: RatesState) => {
                  state.rooms[idx].isFetchingPackages = false
                  if (rooms) {
                    state.rooms[idx].rooms = rooms
                    const rateSummaryRoom = state.rateSummary[idx]
                    if (rateSummaryRoom) {
                      const room = state.rooms[idx].bookingRoom
                      const selectedRoom = findSelectedRate(
                        room.rateCode,
                        room.counterRateCode,
                        room.roomTypeCode,
                        rooms
                      )

                      if (selectedRoom) {
                        rateSummaryRoom.packages =
                          state.rooms[idx].selectedPackages
                      } else {
                        const searchParams = clearRoomSelectionFromUrl(
                          idx,
                          state.searchParams
                        )
                        state.searchParams = searchParams
                        state.rateSummary[idx] = null
                        state.rooms[idx].selectedRate = null

                        window.history.replaceState(
                          {},
                          "",
                          `${pathname}?${searchParams}`
                        )
                      }
                    }
                  } else {
                    state.rooms[idx].rooms = []
                    if (state.rateSummary[idx]) {
                      const searchParams = clearRoomSelectionFromUrl(
                        idx,
                        state.searchParams
                      )
                      state.searchParams = searchParams
                      state.rateSummary[idx] = null
                      state.rooms[idx].selectedRate = null
                      window.history.replaceState(
                        {},
                        "",
                        `${pathname}?${searchParams}`
                      )
                    }
                  }
                })
              )
            },
          },

          bookingRoom: room,
          isFetchingAdditionalRate: false,
          isFetchingPackages: false,
          rooms: roomConfiguration,
          selectedFilter,
          selectedPackages,
          selectedRate:
            selectedRate && product
              ? {
                  features: selectedRate.features,
                  packages: selectedPackages,
                  product,
                  roomType: selectedRate.roomType,
                  roomTypeCode: selectedRate.roomTypeCode,
                }
              : null,
        }
      }),
    }
  })
}

export function useRatesStore<T>(selector: (store: RatesState) => T) {
  const store = useContext(RatesContext)

  if (!store) {
    throw new Error("useRatesStore must be used within RatesProvider")
  }

  return useStore(store, selector)
}
