import { differenceInCalendarDays, format, isWeekend } from "date-fns"

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

import { sumPackages } from "@/components/HotelReservation/utils"
import { getSpecialRoomType } from "@/utils/specialRoomType"

import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
import type { DetailsBooking } from "@/types/components/hotelReservation/enterDetails/details"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import {
  TrackingChannelEnum,
  type TrackingSDKAncillaries,
  type TrackingSDKHotelInfo,
  type TrackingSDKPageData,
} from "@/types/components/tracking"
import { CurrencyEnum } from "@/types/enums/currency"
import { PackageTypeEnum } from "@/types/enums/packages"
import type { Hotel } from "@/types/hotel"
import type { Room } from "@/types/providers/details/room"
import type { RoomState } from "@/types/stores/enter-details"
import type {
  PriceProduct,
  Product,
} from "@/types/trpc/routers/hotel/roomAvailability"
import type { Lang } from "@/constants/languages"

export function getTracking(
  booking: DetailsBooking,
  hotel: Hotel,
  rooms: Room[],
  isMember: boolean,
  lang: Lang,
  storedRooms: RoomState[],
  breakfastPackages: BreakfastPackages,
  searchTerm?: string
) {
  const arrivalDate = new Date(booking.fromDate)
  const departureDate = new Date(booking.toDate)
  const shouldSkipBreakfastOption =
    storedRooms.every((r) => r.room.breakfast === undefined) ||
    !breakfastPackages.length

  const breakfastOption = shouldSkipBreakfastOption
    ? undefined
    : storedRooms
        .map((r) => {
          if (r.room.breakfast === undefined) return ""
          if (!r.room.breakfast) return "no breakfast"
          return "breakfast buffet"
        })
        .join("|")

  const pageTrackingData: TrackingSDKPageData = {
    channel: TrackingChannelEnum.hotelreservation,
    domainLanguage: lang,
    pageId: "details",
    pageName: "hotelreservation|details",
    pageType: "bookingenterdetailspage",
    siteSections: "hotelreservation|details",
    siteVersion: "new-web",
  }
  const hotelsTrackingData: TrackingSDKHotelInfo = {
    ageOfChildren: booking.rooms
      .map((room) => room.childrenInRoom?.map((kid) => kid.age).join(",") ?? "")
      .join("|"),
    analyticsRateCode: rooms.map((room) => room.rate).join("|"),
    arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
    bedType: storedRooms
      .map((r) => (r.room.bedType ? r.room.bedType.description : ""))
      .join("|"),
    // Comma separated booking code values in "code,code,n/a" format for multiroom and "code" or "n/a" for singleroom
    // n/a is used whenever code is Not applicable as defined by Tracking team
    bookingCode: rooms
      .map((room) => room.roomRate.bookingCode ?? "n/a")
      .join(", "),
    // Similar to booking code, comma separated room based values.
    bookingCodeAvailability: booking.bookingCode
      ? rooms
          .map((room) => (room.roomRate.bookingCode ? "true" : "false"))
          .join(", ")
      : undefined,
    bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
    breakfastOption,
    childBedPreference: booking.rooms
      .map(
        (room) =>
          room.childrenInRoom
            ?.map((kid) => ChildBedMapEnum[kid.bed])
            .join(",") ?? ""
      )
      .join("|"),
    country: hotel?.address.country,
    departureDate: format(departureDate, "yyyy-MM-dd"),
    discount: storedRooms.reduce((total, { room }, idx) => {
      const isMainRoom = idx === 0
      if (
        hasMemberPrice(room.roomRate) &&
        isMainRoom &&
        isMember &&
        hasPublicPrice(room.roomRate)
      ) {
        const memberPrice = room.roomRate.member.localPrice.pricePerStay
        const publicPrice = room.roomRate.public.localPrice.pricePerStay
        total += publicPrice - memberPrice
      } else if (
        hasPublicPrice(room.roomRate) &&
        room.roomRate.public.localPrice.regularPricePerStay
      ) {
        const publicPrice = room.roomRate.public.localPrice.pricePerStay
        const regularPrice = room.roomRate.public.localPrice.regularPricePerStay
        total += regularPrice - publicPrice
      }
      return total
    }, 0),
    duration: differenceInCalendarDays(departureDate, arrivalDate),
    hotelID: hotel?.operaId,
    leadTime: differenceInCalendarDays(arrivalDate, new Date()),
    noOfAdults: booking.rooms.map((room) => room.adults).join(","),
    noOfChildren: booking.rooms
      .map((room) => room.childrenInRoom?.length ?? 0)
      .join(","),
    noOfRooms: booking.rooms.length,
    ...(rooms.length > 1 && {
      multiroomRateIdentity: rooms.every((room) => {
        const firstRoom = rooms[0]
        if (
          hasPublicPrice(firstRoom.roomRate) &&
          hasPublicPrice(room.roomRate)
        ) {
          return (
            firstRoom.roomRate.public?.localPrice.pricePerNight ===
            room.roomRate.public?.localPrice.pricePerNight
          )
        }
      })
        ? "same rate"
        : "mixed rate",
    }),
    rateCode: rooms
      .map((room, idx) => {
        const isMainRoom = idx === 0
        if (hasMemberPrice(room.roomRate) && isMember && isMainRoom) {
          return room.roomRate.member.rateCode
        } else if (hasPublicPrice(room.roomRate)) {
          return room.roomRate.public.rateCode
        } else if ("corporateCheque" in room.roomRate) {
          return room.roomRate.corporateCheque.rateCode
        } else if ("redemption" in room.roomRate) {
          return room.roomRate.redemption.rateCode
        } else if ("voucher" in room.roomRate) {
          return room.roomRate.voucher.rateCode
        }
        return ""
      })
      .join("|"),

    rateCodeCancellationRule: rooms
      .map((room) => room.cancellationText.toLowerCase())
      .join(","),
    rateCodeName: rooms.map((room) => room.rateDefinitionTitle).join(","),
    rateCodeType: rooms.map((room) => room.rateType.toLowerCase()).join(","),
    region: hotel?.address.city,
    revenueCurrencyCode: [
      ...new Set(
        rooms.map((room) => {
          if ("corporateCheque" in room.roomRate) {
            return CurrencyEnum.CC
          } else if ("redemption" in room.roomRate) {
            return CurrencyEnum.POINTS
          } else if ("voucher" in room.roomRate) {
            return CurrencyEnum.Voucher
          } else if (hasPublicPrice(room.roomRate)) {
            return room.roomRate.public.localPrice.currency
          } else if (hasMemberPrice(room.roomRate)) {
            return room.roomRate.member.localPrice.currency
          }
          return CurrencyEnum.Unknown
        })
      ),
    ].join(","),
    rewardNight: booking.searchType === REDEMPTION ? "yes" : "no",
    rewardNightAvailability:
      booking.searchType === REDEMPTION ? "true" : "false",
    roomPrice: calcTotalRoomPrice(storedRooms, isMember),
    roomTypeCode: rooms.map((room) => room.roomTypeCode).join("|"),
    roomTypeName: rooms.map((room) => room.roomType).join("|"),
    totalPrice: calcTotalPrice(storedRooms, isMember),
    points:
      // @ts-expect-error - redemption object doesn't exist error
      rooms.find((room) => "redemption" in room.roomRate)?.roomRate.redemption
        .localPrice.pointsPerStay ?? "n/a",
    searchTerm,
    searchType: "hotel",
    specialRoomType: rooms
      .map((room) => getSpecialRoomType(room.packages))
      .join(","),
  }
  const roomsWithPetRoom = rooms.filter(hasPetRoom)
  const petRoomAncillaries: TrackingSDKAncillaries = roomsWithPetRoom
    .slice(0, 1) // should only be one item
    .map((room) => {
      return room.packages
        .filter((p) => p.code === RoomPackageCodeEnum.PET_ROOM)
        .map((pkg) => ({
          hotelId: hotel.operaId,
          productId: pkg.code,
          productUnits: roomsWithPetRoom.length,
          productPoints: 0,
          productPrice: pkg.localPrice.totalPrice * roomsWithPetRoom.length,
          productType: "room preference",
          productName: "pet room",
          productCategory: "",
        }))[0]
    })

  const breakfastAncillaries: TrackingSDKAncillaries = storedRooms
    .map((room) => {
      return { breakfast: room.room.breakfast, adults: room.room.adults }
    })
    .filter(hasBreakfast)
    .map(({ breakfast, adults }) => {
      return {
        hotelId: hotel.operaId,
        productId: breakfast.code,
        productUnits:
          adults * differenceInCalendarDays(departureDate, arrivalDate),
        productPoints: 0,
        productPrice:
          breakfast.localPrice.price *
          adults *
          differenceInCalendarDays(departureDate, arrivalDate),
        productType: "food",
        productName: breakfast.description,
        productCategory: breakfast.packageType,
      }
    })
  const ancillaries = petRoomAncillaries.concat(breakfastAncillaries)

  return {
    hotelsTrackingData,
    pageTrackingData,
    ancillaries,
  }
}

function hasPublicPrice(
  roomRate: Product
): roomRate is PriceProduct & { public: NonNullable<PriceProduct["public"]> } {
  if ("public" in roomRate && roomRate.public) {
    return true
  }
  return false
}

function hasMemberPrice(
  roomRate: Product
): roomRate is PriceProduct & { member: NonNullable<PriceProduct["member"]> } {
  if ("member" in roomRate && roomRate.member) {
    return true
  }
  return false
}

function hasPetRoom(
  room: Room
): room is Room & { packages: NonNullable<Room["packages"]> } {
  if (!room.packages) {
    return false
  }
  return room.packages.some((p) => p.code === RoomPackageCodeEnum.PET_ROOM)
}

type RoomEntry = {
  breakfast?: BreakfastPackages[number] | false
  adults: number
}

function hasBreakfast(entry: RoomEntry): entry is RoomEntry & {
  breakfast: BreakfastPackages[number]
  adults: number
} {
  return (
    entry.breakfast !== false &&
    entry.breakfast?.packageType === PackageTypeEnum.BreakfastAdult
  )
}

function calcTotalPrice(rooms: RoomState[], isMember: boolean) {
  const totalRoomPrice = calcTotalRoomPrice(rooms, isMember)
  const totalPackageSum = rooms.reduce((total, { room }) => {
    if (room.breakfast) {
      total += Number(room.breakfast.localPrice.totalPrice) * room.adults
    }
    if (room.roomFeatures?.length) {
      const packageSum = sumPackages(room.roomFeatures)
      total += packageSum.price
    }
    return total
  }, 0)
  return totalRoomPrice + totalPackageSum
}

function calcTotalRoomPrice(rooms: RoomState[], isMember: boolean) {
  return rooms.reduce((total, { room }, idx) => {
    // When it comes special rates, only redemption has additional price and that should be added
    // other special rates like voucher, corporateCheck should be added as 0 according to Priyanka
    if ("redemption" in room.roomRate) {
      return room.roomRate.redemption.localPrice?.additionalPricePerStay ?? 0
    } else if (
      "corporateCheck" in room.roomRate ||
      "voucher" in room.roomRate
    ) {
      return 0
    }
    const isMainRoom = idx === 0
    if (hasMemberPrice(room.roomRate) && isMember && isMainRoom) {
      total += room.roomRate.member.localPrice.pricePerStay
    } else if (hasPublicPrice(room.roomRate)) {
      total += room.roomRate.public.localPrice.pricePerStay
    }
    return total
  }, 0)
}
