import isEqual from "fast-deep-equal"
import { parsePhoneNumberFromString } from "libphonenumber-js"

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

import { detailsStorageName } from "."

import { type RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
import type { Price } from "@/types/components/hotelReservation/price"
import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate"
import { CurrencyEnum } from "@/types/enums/currency"
import type { Package } from "@/types/requests/packages"
import type { PersistedState, RoomState } from "@/types/stores/enter-details"
import type { SafeUser } from "@/types/user"

export function extractGuestFromUser(user: NonNullable<SafeUser>) {
  let phoneNumberCC = ""
  if (user.phoneNumber) {
    const parsedPhoneNumber = parsePhoneNumberFromString(user.phoneNumber)
    if (parsedPhoneNumber?.country) {
      phoneNumberCC = parsedPhoneNumber.country.toLowerCase()
    }
  }
  return {
    countryCode: user.address.countryCode?.toString(),
    email: user.email,
    firstName: user.firstName,
    lastName: user.lastName,
    join: false,
    membershipNo: user.membership?.membershipNumber,
    phoneNumber: user.phoneNumber ?? "",
    phoneNumberCC,
  }
}

export function checkIsSameBooking(
  prev: SelectRateBooking & { errorCode?: string },
  next: SelectRateBooking & { errorCode?: string }
) {
  const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev

  const prevRoomsWithoutRateCodes = prevRooms?.map(
    ({ rateCode, counterRateCode, roomTypeCode, bookingCode, ...room }) => room
  )
  const { rooms: nextRooms, errorCode: nextErrorCode, ...nextBooking } = next

  const nextRoomsWithoutRateCodes = nextRooms?.map(
    ({ rateCode, counterRateCode, roomTypeCode, bookingCode, ...room }) => room
  )

  return isEqual(
    {
      ...prevBooking,
      rooms: prevRoomsWithoutRateCodes,
    },
    {
      ...nextBooking,
      rooms: nextRoomsWithoutRateCodes,
    }
  )
}

export function add(...nums: (number | string | undefined)[]) {
  return nums.reduce((total: number, num) => {
    if (typeof num === "undefined") {
      num = 0
    }
    total = total + parseInt(`${num}`)
    return total
  }, 0)
}

export function getRoomPrice(roomRate: RoomRate, isMember: boolean) {
  if (isMember && "member" in roomRate && roomRate.member) {
    return {
      perNight: {
        requested: roomRate.member.requestedPrice
          ? {
              currency: roomRate.member.requestedPrice.currency,
              price: roomRate.member.requestedPrice.pricePerNight,
            }
          : undefined,
        local: {
          currency: roomRate.member.localPrice.currency,
          price: roomRate.member.localPrice.pricePerNight,
        },
      },
      perStay: {
        requested: roomRate.member.requestedPrice
          ? {
              currency: roomRate.member.requestedPrice.currency,
              price: roomRate.member.requestedPrice.pricePerStay,
            }
          : undefined,
        local: {
          currency: roomRate.member.localPrice.currency,
          price: roomRate.member.localPrice.pricePerStay,
        },
      },
    }
  }

  if ("public" in roomRate && roomRate.public) {
    return {
      perNight: {
        requested: roomRate.public.requestedPrice
          ? {
              currency: roomRate.public.requestedPrice.currency,
              price: roomRate.public.requestedPrice.pricePerNight,
            }
          : undefined,
        local: {
          currency: roomRate.public.localPrice.currency,
          price: roomRate.public.localPrice.pricePerNight,
          regularPrice: roomRate.public.localPrice.regularPricePerNight,
        },
      },
      perStay: {
        requested: roomRate.public.requestedPrice
          ? {
              currency: roomRate.public.requestedPrice.currency,
              price: roomRate.public.requestedPrice.pricePerStay,
            }
          : undefined,
        local: {
          currency: roomRate.public.localPrice.currency,
          price: roomRate.public.localPrice.pricePerStay,
          regularPrice: roomRate.public.localPrice.regularPricePerStay,
        },
      },
    }
  }

  if ("corporateCheque" in roomRate) {
    return {
      perNight: {
        requested: roomRate.corporateCheque.requestedPrice
          ? {
              currency: CurrencyEnum.CC,
              price: roomRate.corporateCheque.requestedPrice.numberOfCheques,
              additionalPrice:
                roomRate.corporateCheque.requestedPrice.additionalPricePerStay,
              additionalPriceCurrency:
                roomRate.corporateCheque.requestedPrice.currency ?? undefined,
            }
          : undefined,
        local: {
          currency: CurrencyEnum.CC,
          price: roomRate.corporateCheque.localPrice.numberOfCheques,
          additionalPrice:
            roomRate.corporateCheque.localPrice.additionalPricePerStay,
          additionalPriceCurrency:
            roomRate.corporateCheque.localPrice.currency ?? undefined,
        },
      },
      perStay: {
        requested: roomRate.corporateCheque.requestedPrice
          ? {
              currency: CurrencyEnum.CC,
              price: roomRate.corporateCheque.requestedPrice.numberOfCheques,
              additionalPrice:
                roomRate.corporateCheque.requestedPrice.additionalPricePerStay,
              additionalPriceCurrency:
                roomRate.corporateCheque.requestedPrice.currency ?? undefined,
            }
          : undefined,
        local: {
          currency: CurrencyEnum.CC,
          price: roomRate.corporateCheque.localPrice.numberOfCheques,
          additionalPrice:
            roomRate.corporateCheque.localPrice.additionalPricePerStay,
          additionalPriceCurrency:
            roomRate.corporateCheque.localPrice.currency ?? undefined,
        },
      },
    }
  }

  if ("voucher" in roomRate) {
    return {
      perNight: {
        requested: undefined,
        local: {
          currency: CurrencyEnum.Voucher,
          price: roomRate.voucher.numberOfVouchers,
        },
      },
      perStay: {
        requested: undefined,
        local: {
          currency: CurrencyEnum.Voucher,
          price: roomRate.voucher.numberOfVouchers,
        },
      },
    }
  }

  if ("redemption" in roomRate) {
    return {
      // ToDo Handle perNight as undefined
      perNight: {
        requested: undefined,
        local: {
          currency: CurrencyEnum.POINTS,
          price: roomRate.redemption.localPrice.pointsPerStay,
          additionalPrice:
            roomRate.redemption.localPrice.additionalPricePerStay,
          additionalPriceCurrency:
            roomRate.redemption.localPrice.currency ?? undefined,
        },
      },
      perStay: {
        requested: undefined,
        local: {
          currency: CurrencyEnum.POINTS,
          price: roomRate.redemption.localPrice.pointsPerStay,
          additionalPrice:
            roomRate.redemption.localPrice.additionalPricePerStay,
          additionalPriceCurrency:
            roomRate.redemption.localPrice.currency ?? undefined,
        },
      },
    }
  }

  throw new Error(
    `Unable to calculate RoomPrice since user is neither a member or memberRate is missing, or publicRate is missing`
  )
}

export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) {
  const totalPrice = roomRates.reduce<Price>(
    (total, roomRate, idx) => {
      const isMainRoom = idx === 0
      let rate
      if (isMainRoom && isMember && "member" in roomRate && roomRate.member) {
        rate = roomRate.member
      } else if ("public" in roomRate && roomRate.public) {
        rate = roomRate.public
      }
      // TODO: Handle other products?
      if (!rate) {
        return total
      }

      total.local.currency = rate.localPrice.currency
      total.local.price = add(total.local.price, rate.localPrice.pricePerStay)
      if (rate.localPrice.regularPricePerStay) {
        total.local.regularPrice = add(
          total.local.regularPrice,
          rate.localPrice.regularPricePerStay
        )
      }

      if (rate.requestedPrice) {
        if (total.requested) {
          total.requested.price = add(
            total.requested.price,
            rate.requestedPrice.pricePerStay
          )
        } else {
          total.requested = {
            currency: rate.requestedPrice.currency,
            price: rate.requestedPrice.pricePerStay,
          }
        }
      }

      return total
    },
    {
      local: {
        currency: CurrencyEnum.Unknown,
        price: 0,
      },
      requested: undefined,
    }
  )

  if (totalPrice.local.regularPrice) {
    const totalPriceWithRegularPrice = roomRates.reduce(
      (total, roomRate, idx) => {
        const isMainRoom = idx === 0
        let rate
        if (isMainRoom && isMember && "member" in roomRate && roomRate.member) {
          rate = roomRate.member
        } else if ("public" in roomRate && roomRate.public) {
          rate = roomRate.public
        }

        if (!rate) {
          return total
        }

        if (rate.localPrice.regularPricePerStay) {
          total.local.regularPrice =
            total.local.regularPrice + rate.localPrice.regularPricePerStay
        } else {
          total.local.regularPrice =
            total.local.regularPrice + rate.localPrice.pricePerStay
        }

        return total
      },
      {
        ...totalPrice,
        local: {
          ...totalPrice.local,
          regularPrice: 0,
        },
      }
    )

    if (
      totalPriceWithRegularPrice.local.price ===
      totalPriceWithRegularPrice.local.regularPrice
    ) {
      totalPriceWithRegularPrice.local.regularPrice = 0
    }

    return totalPriceWithRegularPrice
  }

  return totalPrice
}

export function calculateVoucherPrice(
  roomRates: RoomRate[],
  packages: Package[]
) {
  return roomRates.reduce<Price>(
    (total, room) => {
      if (!("voucher" in room)) {
        return total
      }

      const pkgsSum = sumPackages(packages)

      return {
        local: {
          additionalPrice: pkgsSum.price,
          additionalPriceCurrency: pkgsSum.currency,
          currency: total.local.currency,
          price: total.local.price + room.voucher.numberOfVouchers,
        },
        requested: undefined,
      }
    },
    {
      local: {
        currency: CurrencyEnum.Voucher,
        price: 0,
      },
      requested: undefined,
    }
  )
}

export function calculateCorporateChequePrice(roomRates: RoomRate[]) {
  return roomRates.reduce<Price>(
    (total, room) => {
      if (!("corporateCheque" in room)) {
        return total
      }

      const rate = room.corporateCheque

      total.local.price = add(
        total.local.price,
        rate.localPrice.numberOfCheques
      )

      if (rate.localPrice.additionalPricePerStay) {
        total.local.additionalPrice = add(
          total.local.additionalPrice,
          rate.localPrice.additionalPricePerStay
        )
      }
      if (rate.localPrice.currency) {
        total.local.additionalPriceCurrency = rate.localPrice.currency
      }

      if (rate.requestedPrice) {
        if (total.requested) {
          total.requested.price = add(
            total.requested.price,
            rate.requestedPrice.numberOfCheques
          )
        } else {
          total.requested = {
            currency: CurrencyEnum.CC,
            price: rate.requestedPrice.numberOfCheques,
          }
        }

        if (rate.requestedPrice.additionalPricePerStay) {
          total.requested.additionalPrice = add(
            total.requested.additionalPrice,
            rate.requestedPrice.additionalPricePerStay
          )
        }

        if (rate.requestedPrice.currency) {
          total.requested.additionalPriceCurrency = rate.requestedPrice.currency
        }
      }

      return total
    },
    {
      local: {
        currency: CurrencyEnum.CC,
        price: 0,
      },
      requested: undefined,
    }
  )
}

export function calcTotalPrice(
  rooms: RoomState[],
  currency: Price["local"]["currency"],
  isMember: boolean,
  nights: number
) {
  return rooms.reduce<Price>(
    (acc, { room }, index) => {
      const isFirstRoomAndMember = index === 0 && isMember
      const join = Boolean(room.guest.join || room.guest.membershipNo)

      const roomPrice = getRoomPrice(
        room.roomRate,
        isFirstRoomAndMember || join
      )

      if (!roomPrice) {
        return acc
      }

      const isSpecialRate =
        "corporateCheque" in room.roomRate ||
        "redemption" in room.roomRate ||
        "voucher" in room.roomRate

      const breakfastRequestedPrice = room.breakfast
        ? (room.breakfast.requestedPrice?.price ?? 0)
        : 0
      const breakfastLocalPrice = room.breakfast
        ? (room.breakfast.localPrice?.price ?? 0)
        : 0

      const pkgsSum = sumPackages(room.roomFeatures)
      const pkgsSumRequested = sumPackagesRequestedPrice(room.roomFeatures)

      const breakfastRequestedTotalPrice =
        breakfastRequestedPrice * room.adults * nights
      if (roomPrice.perStay.requested) {
        if (!acc.requested) {
          acc.requested = {
            currency: roomPrice.perStay.requested.currency,
            price: 0,
          }
        }

        if (isSpecialRate) {
          acc.requested.price = add(
            acc.requested.price,
            roomPrice.perStay.requested.price
          )

          acc.requested.additionalPrice = add(
            breakfastRequestedTotalPrice,
            pkgsSumRequested.price
          )

          if (!acc.requested.additionalPriceCurrency) {
            if (roomPrice.perStay.requested.additionalPriceCurrency) {
              acc.requested.additionalPriceCurrency =
                roomPrice.perStay.requested.additionalPriceCurrency
            } else if (room.breakfast) {
              acc.requested.additionalPriceCurrency =
                room.breakfast.localPrice.currency
            } else if (pkgsSumRequested.currency) {
              acc.requested.additionalPriceCurrency = pkgsSumRequested.currency
            }
          }
        } else {
          acc.requested.price = add(
            acc.requested.price,
            roomPrice.perStay.requested.price,
            breakfastRequestedTotalPrice,
            pkgsSumRequested.price
          )
        }
      }

      const breakfastLocalTotalPrice =
        breakfastLocalPrice * room.adults * nights

      if (isSpecialRate) {
        acc.local.price = add(acc.local.price, roomPrice.perStay.local.price)

        if (
          roomPrice.perStay.local.additionalPrice ||
          breakfastLocalTotalPrice ||
          pkgsSum.price
        ) {
          acc.local.additionalPrice = add(
            acc.local.additionalPrice,
            roomPrice.perStay.local.additionalPrice,
            breakfastLocalTotalPrice,
            pkgsSum.price
          )
        }

        if (!acc.local.additionalPriceCurrency) {
          if (roomPrice.perStay.local.additionalPriceCurrency) {
            acc.local.additionalPriceCurrency =
              roomPrice.perStay.local.additionalPriceCurrency
          } else if (room.breakfast) {
            acc.local.additionalPriceCurrency =
              room.breakfast.localPrice.currency
          } else if (pkgsSum.currency) {
            acc.local.additionalPriceCurrency = pkgsSum.currency
          }
        }
      } else {
        acc.local.price = add(
          acc.local.price,
          roomPrice.perStay.local.price,
          breakfastLocalTotalPrice,
          pkgsSum.price
        )

        if (roomPrice.perStay.local.regularPrice) {
          acc.local.regularPrice = add(
            acc.local.regularPrice,
            roomPrice.perStay.local.regularPrice,
            breakfastLocalTotalPrice,
            pkgsSum.price
          )
        }
      }

      return acc
    },
    {
      requested: undefined,
      local: { currency, price: 0 },
    }
  )
}

export const checkRoomProgress = (steps: RoomState["steps"]) => {
  return Object.values(steps)
    .filter(Boolean)
    .every((step) => step.isValid)
}

export function readFromSessionStorage(): PersistedState | undefined {
  if (typeof window === "undefined") {
    return undefined
  }

  try {
    const storedData = sessionStorage.getItem(detailsStorageName)
    if (!storedData) {
      return undefined
    }

    const parsedData = JSON.parse(storedData) as PersistedState

    if (!parsedData.booking || !parsedData.rooms) {
      return undefined
    }

    return parsedData
  } catch (error) {
    console.error("Error reading from session storage:", error)
    return undefined
  }
}

export function writeToSessionStorage(state: PersistedState) {
  if (typeof window === "undefined") {
    return
  }

  try {
    sessionStorage.setItem(detailsStorageName, JSON.stringify(state))
  } catch (error) {
    console.error("Error writing to session storage:", error)
  }
}

export function clearSessionStorage() {
  if (typeof window === "undefined") {
    return
  }
  sessionStorage.removeItem(detailsStorageName)
}
