import { metrics } from "@opentelemetry/api"
import { cache } from "react"

import { Lang } from "@/constants/languages"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { request } from "@/lib/graphql/request"
import {
  badRequestError,
  notFound,
  serverErrorByStatus,
} from "@/server/errors/trpc"
import {
  contentStackUidWithServiceProcedure,
  publicProcedure,
  router,
  safeProtectedServiceProcedure,
  serviceProcedure,
} from "@/server/trpc"
import { toApiLang } from "@/server/utils"

import { hotelPageSchema } from "../contentstack/hotelPage/output"
import {
  fetchHotelPageRefs,
  generatePageTags,
  getHotelPageCounter,
  validateHotelPageRefs,
} from "../contentstack/hotelPage/utils"
import { getVerifiedUser, parsedUser } from "../user/query"
import {
  getBreakfastPackageInputSchema,
  getHotelDataInputSchema,
  getHotelsAvailabilityInputSchema,
  getRatesInputSchema,
  getRoomPackagesInputSchema,
  getRoomsAvailabilityInputSchema,
  getSelectedRoomAvailabilityInputSchema,
  type HotelDataInput,
} from "./input"
import {
  breakfastPackagesSchema,
  getHotelDataSchema,
  getHotelsAvailabilitySchema,
  getRatesSchema,
  getRoomPackagesSchema,
  getRoomsAvailabilitySchema,
} from "./output"
import tempRatesData from "./tempRatesData.json"
import {
  getCitiesByCountry,
  getCountries,
  getLocations,
  TWENTYFOUR_HOURS,
} from "./utils"

import { FacilityCardTypeEnum } from "@/types/components/hotelPage/facilities"
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import { HotelTypeEnum } from "@/types/enums/hotelType"
import type { RequestOptionsWithOutBody } from "@/types/fetch"
import type { Facility } from "@/types/hotel"
import type { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage"

const meter = metrics.getMeter("trpc.hotels")
const getHotelCounter = meter.createCounter("trpc.hotel.get")
const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success")
const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail")

const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get")
const getPackagesSuccessCounter = meter.createCounter(
  "trpc.hotel.packages.get-success"
)
const getPackagesFailCounter = meter.createCounter(
  "trpc.hotel.packages.get-fail"
)

const hotelsAvailabilityCounter = meter.createCounter(
  "trpc.hotel.availability.hotels"
)
const hotelsAvailabilitySuccessCounter = meter.createCounter(
  "trpc.hotel.availability.hotels-success"
)
const hotelsAvailabilityFailCounter = meter.createCounter(
  "trpc.hotel.availability.hotels-fail"
)

const roomsAvailabilityCounter = meter.createCounter(
  "trpc.hotel.availability.rooms"
)
const roomsAvailabilitySuccessCounter = meter.createCounter(
  "trpc.hotel.availability.rooms-success"
)
const roomsAvailabilityFailCounter = meter.createCounter(
  "trpc.hotel.availability.rooms-fail"
)

const selectedRoomAvailabilityCounter = meter.createCounter(
  "trpc.hotel.availability.room"
)
const selectedRoomAvailabilitySuccessCounter = meter.createCounter(
  "trpc.hotel.availability.room-success"
)
const selectedRoomAvailabilityFailCounter = meter.createCounter(
  "trpc.hotel.availability.room-fail"
)

const breakfastPackagesCounter = meter.createCounter("trpc.package.breakfast")
const breakfastPackagesSuccessCounter = meter.createCounter(
  "trpc.package.breakfast-success"
)
const breakfastPackagesFailCounter = meter.createCounter(
  "trpc.package.breakfast-fail"
)

async function getContentstackData(lang: Lang, uid?: string | null) {
  if (!uid) {
    return null
  }
  const contentPageRefsData = await fetchHotelPageRefs(lang, uid)
  const contentPageRefs = validateHotelPageRefs(contentPageRefsData, lang, uid)
  if (!contentPageRefs) {
    return null
  }

  const tags = generatePageTags(contentPageRefs, lang)

  getHotelPageCounter.add(1, { lang, uid })
  console.info(
    "contentstack.hotelPage start",
    JSON.stringify({
      query: { lang, uid },
    })
  )
  const response = await request<GetHotelPageData>(
    GetHotelPage,
    {
      locale: lang,
      uid,
    },
    {
      cache: "force-cache",
      next: {
        tags,
      },
    }
  )

  if (!response.data) {
    throw notFound(response)
  }

  const hotelPageData = hotelPageSchema.safeParse(response.data)
  if (!hotelPageData.success) {
    console.error(
      `Failed to validate Hotel Page - (uid: ${uid}, lang: ${lang})`
    )
    console.error(hotelPageData.error)
    return null
  }

  return hotelPageData.data.hotel_page
}

export const getHotelData = cache(
  async (input: HotelDataInput, serviceToken: string) => {
    const { hotelId, language, isCardOnlyPayment } = input

    const params: Record<string, string> = {
      hotelId,
      language,
    }

    params.include = "RoomCategories" // "RoomCategories","NearbyHotels","Restaurants","City",

    getHotelCounter.add(1, {
      hotelId,
      language,
    })
    console.info(
      "api.hotels.hotelData start",
      JSON.stringify({ query: { hotelId, params } })
    )

    const apiResponse = await api.get(
      api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
      {
        headers: {
          Authorization: `Bearer ${serviceToken}`,
        },
        // needs to clear default option as only
        // cache or next.revalidate is permitted
        cache: undefined,
        next: {
          revalidate: 60 * 30, // 30 minutes
        },
      },
      params
    )

    if (!apiResponse.ok) {
      const text = await apiResponse.text()
      getHotelFailCounter.add(1, {
        hotelId,
        language,
        error_type: "http_error",
        error: JSON.stringify({
          status: apiResponse.status,
          statusText: apiResponse.statusText,
          text,
        }),
      })
      console.error(
        "api.hotels.hotelData error",
        JSON.stringify({
          query: { hotelId, params },
          error: {
            status: apiResponse.status,
            statusText: apiResponse.statusText,
            text,
          },
        })
      )
      return null
    }

    const apiJson = await apiResponse.json()
    const validateHotelData = getHotelDataSchema.safeParse(apiJson)

    if (!validateHotelData.success) {
      getHotelFailCounter.add(1, {
        hotelId,
        language,
        error_type: "validation_error",
        error: JSON.stringify(validateHotelData.error),
      })

      console.error(
        "api.hotels.hotelData validation error",
        JSON.stringify({
          query: { hotelId, params },
          error: validateHotelData.error,
        })
      )
      throw badRequestError()
    }

    getHotelSuccessCounter.add(1, {
      hotelId,
      language,
    })
    console.info(
      "api.hotels.hotelData success",
      JSON.stringify({
        query: { hotelId, params: params },
      })
    )
    const hotelData = validateHotelData.data

    if (isCardOnlyPayment) {
      hotelData.data.attributes.merchantInformationData.alternatePaymentOptions =
        []
    }
    if (hotelData.data.attributes.gallery) {
      const smallerImages = hotelData.data.attributes.gallery.smallerImages
      const hotelGalleryImages =
        hotelData.data.attributes.hotelType === HotelTypeEnum.Signature
          ? smallerImages.slice(0, 10)
          : smallerImages.slice(0, 6)
      hotelData.data.attributes.galleryImages = hotelGalleryImages
    }

    return hotelData
  }
)

export const hotelQueryRouter = router({
  get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
    const { lang, uid } = ctx

    const contentstackData = await getContentstackData(lang, uid)
    const hotelId = contentstackData?.hotel_page_id

    if (!hotelId) {
      throw notFound(`Hotel not found for uid: ${uid}`)
    }

    const hotelData = await getHotelData(
      {
        hotelId,
        language: ctx.lang,
      },
      ctx.serviceToken
    )

    if (!hotelData) {
      throw notFound()
    }

    const included = hotelData.included || []

    const hotelAttributes = hotelData.data.attributes
    const images = hotelAttributes.gallery?.smallerImages
    const hotelAlerts = hotelAttributes.specialAlerts

    const roomCategories = included
      ? included.filter((item) => item.type === "roomcategories")
      : []

    const activities = contentstackData?.content
      ? contentstackData?.content[0]
      : null

    const facilities: Facility[] = [
      {
        ...hotelData.data.attributes.restaurantImages,
        id: FacilityCardTypeEnum.restaurant,
        headingText:
          hotelData?.data.attributes.restaurantImages?.headingText ?? "",
        heroImages:
          hotelData?.data.attributes.restaurantImages?.heroImages ?? [],
      },
      {
        ...hotelData.data.attributes.conferencesAndMeetings,
        id: FacilityCardTypeEnum.conference,
        headingText:
          hotelData?.data.attributes.conferencesAndMeetings?.headingText ?? "",
        heroImages:
          hotelData?.data.attributes.conferencesAndMeetings?.heroImages ?? [],
      },
      {
        ...hotelData.data.attributes.healthAndWellness,
        id: FacilityCardTypeEnum.wellness,
        headingText:
          hotelData?.data.attributes.healthAndWellness?.headingText ?? "",
        heroImages:
          hotelData?.data.attributes.healthAndWellness?.heroImages ?? [],
      },
    ]

    return {
      hotelId,
      hotelName: hotelAttributes.name,
      hotelDescription: hotelAttributes.hotelContent.texts.descriptions.short,
      hotelLocation: hotelAttributes.location,
      hotelAddress: hotelAttributes.address,
      hotelRatings: hotelAttributes.ratings,
      hotelDetailedFacilities: hotelAttributes.detailedFacilities,
      hotelImages: images,
      pointsOfInterest: hotelAttributes.pointsOfInterest,
      roomCategories,
      activitiesCard: activities?.upcoming_activities_card,
      facilities,
      alerts: hotelAlerts,
      faq: contentstackData?.faq,
    }
  }),
  availability: router({
    hotels: serviceProcedure
      .input(getHotelsAvailabilityInputSchema)
      .query(async ({ input, ctx }) => {
        const { lang } = ctx
        const apiLang = toApiLang(lang)
        const {
          cityId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
          attachedProfileId,
        } = input

        const params: Record<string, string | number> = {
          roomStayStartDate,
          roomStayEndDate,
          adults,
          ...(children && { children }),
          promotionCode,
          reservationProfileType,
          attachedProfileId,
          language: apiLang,
        }
        hotelsAvailabilityCounter.add(1, {
          cityId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
        })
        console.info(
          "api.hotels.hotelsAvailability start",
          JSON.stringify({ query: { cityId, params } })
        )
        const apiResponse = await api.get(
          api.endpoints.v1.Availability.city(cityId),
          {
            headers: {
              Authorization: `Bearer ${ctx.serviceToken}`,
            },
          },
          params
        )
        if (!apiResponse.ok) {
          const text = await apiResponse.text()
          hotelsAvailabilityFailCounter.add(1, {
            cityId,
            roomStayStartDate,
            roomStayEndDate,
            adults,
            children,
            promotionCode,
            reservationProfileType,
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text,
            }),
          })
          console.error(
            "api.hotels.hotelsAvailability error",
            JSON.stringify({
              query: { cityId, params },
              error: {
                status: apiResponse.status,
                statusText: apiResponse.statusText,
                text,
              },
            })
          )
          return null
        }
        const apiJson = await apiResponse.json()
        const validateAvailabilityData =
          getHotelsAvailabilitySchema.safeParse(apiJson)
        if (!validateAvailabilityData.success) {
          hotelsAvailabilityFailCounter.add(1, {
            cityId,
            roomStayStartDate,
            roomStayEndDate,
            adults,
            children,
            promotionCode,
            reservationProfileType,
            error_type: "validation_error",
            error: JSON.stringify(validateAvailabilityData.error),
          })
          console.error(
            "api.hotels.hotelsAvailability validation error",
            JSON.stringify({
              query: { cityId, params },
              error: validateAvailabilityData.error,
            })
          )
          throw badRequestError()
        }
        hotelsAvailabilitySuccessCounter.add(1, {
          cityId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
        })
        console.info(
          "api.hotels.hotelsAvailability success",
          JSON.stringify({
            query: { cityId, params: params },
          })
        )
        return {
          availability: validateAvailabilityData.data.data
            .filter(
              (hotels) =>
                hotels.attributes.status === AvailabilityEnum.Available
            )
            .flatMap((hotels) => hotels.attributes),
        }
      }),
    rooms: serviceProcedure
      .input(getRoomsAvailabilityInputSchema)
      .query(async ({ input, ctx }) => {
        const {
          hotelId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
          attachedProfileId,
          rateCode,
        } = input

        const params: Record<string, string | number | undefined> = {
          roomStayStartDate,
          roomStayEndDate,
          adults,
          ...(children && { children }),
          promotionCode,
          reservationProfileType,
          attachedProfileId,
        }

        roomsAvailabilityCounter.add(1, {
          hotelId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
        })
        console.info(
          "api.hotels.roomsAvailability start",
          JSON.stringify({ query: { hotelId, params } })
        )
        const apiResponse = await api.get(
          api.endpoints.v1.Availability.hotel(hotelId.toString()),
          {
            headers: {
              Authorization: `Bearer ${ctx.serviceToken}`,
            },
          },
          params
        )

        if (!apiResponse.ok) {
          const text = await apiResponse.text()
          roomsAvailabilityFailCounter.add(1, {
            hotelId,
            roomStayStartDate,
            roomStayEndDate,
            adults,
            children,
            promotionCode,
            reservationProfileType,
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text,
            }),
          })
          console.error(
            "api.hotels.roomsAvailability error",
            JSON.stringify({
              query: { hotelId, params },
              error: {
                status: apiResponse.status,
                statusText: apiResponse.statusText,
                text,
              },
            })
          )
          return null
        }
        const apiJson = await apiResponse.json()
        const validateAvailabilityData =
          getRoomsAvailabilitySchema.safeParse(apiJson)
        if (!validateAvailabilityData.success) {
          roomsAvailabilityFailCounter.add(1, {
            hotelId,
            roomStayStartDate,
            roomStayEndDate,
            adults,
            children,
            promotionCode,
            reservationProfileType,
            error_type: "validation_error",
            error: JSON.stringify(validateAvailabilityData.error),
          })
          console.error(
            "api.hotels.roomsAvailability validation error",
            JSON.stringify({
              query: { hotelId, params },
              error: validateAvailabilityData.error,
            })
          )
          throw badRequestError()
        }
        roomsAvailabilitySuccessCounter.add(1, {
          hotelId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
        })
        console.info(
          "api.hotels.roomsAvailability success",
          JSON.stringify({
            query: { hotelId, params: params },
          })
        )

        if (rateCode) {
          validateAvailabilityData.data.mustBeGuaranteed =
            validateAvailabilityData.data.rateDefinitions.filter(
              (rate) => rate.rateCode === rateCode
            )[0].mustBeGuaranteed
        }

        return validateAvailabilityData.data
      }),
    room: serviceProcedure
      .input(getSelectedRoomAvailabilityInputSchema)
      .query(async ({ input, ctx }) => {
        const {
          hotelId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
          attachedProfileId,
          rateCode,
          roomTypeCode,
          packageCodes,
        } = input

        const params: Record<string, string | number | undefined> = {
          roomStayStartDate,
          roomStayEndDate,
          adults,
          ...(children && { children }),
          promotionCode,
          reservationProfileType,
          attachedProfileId,
          language: toApiLang(ctx.lang),
        }

        selectedRoomAvailabilityCounter.add(1, {
          hotelId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
        })
        console.info(
          "api.hotels.selectedRoomAvailability start",
          JSON.stringify({ query: { hotelId, params } })
        )
        const apiResponseAvailability = await api.get(
          api.endpoints.v1.Availability.hotel(hotelId.toString()),
          {
            headers: {
              Authorization: `Bearer ${ctx.serviceToken}`,
            },
          },
          params
        )

        if (!apiResponseAvailability.ok) {
          const text = await apiResponseAvailability.text()
          selectedRoomAvailabilityFailCounter.add(1, {
            hotelId,
            roomStayStartDate,
            roomStayEndDate,
            adults,
            children,
            promotionCode,
            reservationProfileType,
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponseAvailability.status,
              statusText: apiResponseAvailability.statusText,
              text,
            }),
          })
          console.error(
            "api.hotels.selectedRoomAvailability error",
            JSON.stringify({
              query: { hotelId, params },
              error: {
                status: apiResponseAvailability.status,
                statusText: apiResponseAvailability.statusText,
                text,
              },
            })
          )
          return null
        }
        const apiJsonAvailability = await apiResponseAvailability.json()
        const validateAvailabilityData =
          getRoomsAvailabilitySchema.safeParse(apiJsonAvailability)
        if (!validateAvailabilityData.success) {
          selectedRoomAvailabilityFailCounter.add(1, {
            hotelId,
            roomStayStartDate,
            roomStayEndDate,
            adults,
            children,
            promotionCode,
            reservationProfileType,
            error_type: "validation_error",
            error: JSON.stringify(validateAvailabilityData.error),
          })
          console.error(
            "api.hotels.selectedRoomAvailability validation error",
            JSON.stringify({
              query: { hotelId, params },
              error: validateAvailabilityData.error,
            })
          )
          throw badRequestError()
        }

        const hotelData = await getHotelData(
          {
            hotelId,
            language: ctx.lang,
          },
          ctx.serviceToken
        )

        const availableRooms =
          validateAvailabilityData.data.roomConfigurations.filter((room) => {
            if (packageCodes) {
              return (
                room.status === "Available" &&
                room.features.some(
                  (feature) =>
                    packageCodes.includes(feature.code) && feature.inventory > 0
                )
              )
            }
            return room.status === "Available"
          })

        const selectedRoom = availableRooms.find(
          (room) => room.roomTypeCode === roomTypeCode
        )

        const availableRoomsInCategory = availableRooms.filter(
          (room) => room.roomType === selectedRoom?.roomType
        )
        if (!selectedRoom) {
          console.error("No matching room found")
          return null
        }

        const rateTypes = selectedRoom.products.find(
          (rate) =>
            rate.productType.public.rateCode === rateCode ||
            rate.productType.member?.rateCode === rateCode
        )

        if (!rateTypes) {
          console.error("No matching rate found")
          return null
        }
        const rates = rateTypes.productType

        const mustBeGuaranteed =
          validateAvailabilityData.data.rateDefinitions.filter(
            (rate) => rate.rateCode === rateCode
          )[0].mustBeGuaranteed

        const cancellationText =
          validateAvailabilityData.data.rateDefinitions.find(
            (rate) => rate.rateCode === rateCode
          )?.cancellationText ?? ""

        const bedTypes = availableRoomsInCategory
          .map((availRoom) => {
            const matchingRoom = hotelData?.included
              ?.find((room) => room.name === availRoom.roomType)
              ?.roomTypes.find(
                (roomType) => roomType.code === availRoom.roomTypeCode
              )

            if (matchingRoom) {
              return {
                description: matchingRoom.mainBed.description,
                size: matchingRoom.mainBed.widthRange,
                value: matchingRoom.code,
              }
            }
          })
          .filter((bed): bed is BedTypeSelection => Boolean(bed))

        selectedRoomAvailabilitySuccessCounter.add(1, {
          hotelId,
          roomStayStartDate,
          roomStayEndDate,
          adults,
          children,
          promotionCode,
          reservationProfileType,
        })
        console.info(
          "api.hotels.selectedRoomAvailability success",
          JSON.stringify({
            query: { hotelId, params: params },
          })
        )

        return {
          selectedRoom,
          mustBeGuaranteed,
          cancellationText,
          memberRate: rates?.member,
          publicRate: rates.public,
          bedTypes,
        }
      }),
  }),
  rates: router({
    get: publicProcedure
      .input(getRatesInputSchema)
      .query(async ({ input, ctx }) => {
        // TODO: Do a real API call when the endpoint is ready
        // const { hotelId } = input

        // const params = new URLSearchParams()
        // const apiLang = toApiLang(language)
        // params.set("hotelId", hotelId.toString())
        // params.set("language", apiLang)

        console.info("api.hotels.rates start", JSON.stringify({}))
        const validatedHotelData = getRatesSchema.safeParse(tempRatesData)

        if (!tempRatesData) {
          console.error(
            "api.hotels.rates error",
            JSON.stringify({ error: null })
          )
          //Can't return null here since consuming component does not handle null yet
          // return null
        }
        if (!validatedHotelData.success) {
          console.error(
            "api.hotels.rates validation error",
            JSON.stringify({
              error: validatedHotelData.error,
            })
          )
          throw badRequestError()
        }
        console.info("api.hotels.rates success", JSON.stringify({}))
        return validatedHotelData.data
      }),
  }),
  hotelData: router({
    get: serviceProcedure
      .input(getHotelDataInputSchema)
      .query(async ({ ctx, input }) => {
        return getHotelData(input, ctx.serviceToken)
      }),
  }),
  locations: router({
    get: serviceProcedure.query(async function ({ ctx }) {
      const searchParams = new URLSearchParams()
      searchParams.set("language", toApiLang(ctx.lang))

      const options: RequestOptionsWithOutBody = {
        // needs to clear default option as only
        // cache or next.revalidate is permitted
        cache: undefined,
        headers: {
          Authorization: `Bearer ${ctx.serviceToken}`,
        },
        next: {
          revalidate: TWENTYFOUR_HOURS,
        },
      }

      const countries = await getCountries(options, searchParams, ctx.lang)

      let citiesByCountry = null
      if (countries) {
        citiesByCountry = await getCitiesByCountry(
          countries,
          options,
          searchParams,
          ctx.lang
        )
      }

      const locations = await getLocations(
        ctx.lang,
        options,
        searchParams,
        citiesByCountry
      )

      if (Array.isArray(locations)) {
        return {
          data: locations,
        }
      }

      return locations
    }),
  }),
  packages: router({
    get: serviceProcedure
      .input(getRoomPackagesInputSchema)
      .query(async ({ input, ctx }) => {
        const { hotelId, startDate, endDate, adults, children, packageCodes } =
          input

        const { lang } = ctx

        const apiLang = toApiLang(lang)

        const searchParams = new URLSearchParams({
          startDate,
          endDate,
          adults: adults.toString(),
          children: children.toString(),
          language: apiLang,
        })

        packageCodes.forEach((code) => {
          searchParams.append("packageCodes", code)
        })

        const params = searchParams.toString()

        getPackagesCounter.add(1, {
          hotelId,
        })
        console.info(
          "api.hotels.packages start",
          JSON.stringify({ query: { hotelId, params } })
        )

        const apiResponse = await api.get(
          api.endpoints.v1.Package.Packages.hotel(hotelId),
          {
            headers: {
              Authorization: `Bearer ${ctx.serviceToken}`,
            },
          },
          params
        )

        if (!apiResponse.ok) {
          getPackagesFailCounter.add(1, {
            hotelId,
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
            }),
          })
          console.error(
            "api.hotels.packages error",
            JSON.stringify({ query: { hotelId, params } })
          )
          throw serverErrorByStatus(apiResponse.status, apiResponse)
        }

        const apiJson = await apiResponse.json()
        const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson)

        if (!validatedPackagesData.success) {
          getHotelFailCounter.add(1, {
            hotelId,
            error_type: "validation_error",
            error: JSON.stringify(validatedPackagesData.error),
          })

          console.error(
            "api.hotels.packages validation error",
            JSON.stringify({
              query: { hotelId, params },
              error: validatedPackagesData.error,
            })
          )
          throw badRequestError()
        }

        getPackagesSuccessCounter.add(1, {
          hotelId,
        })
        console.info(
          "api.hotels.packages success",
          JSON.stringify({ query: { hotelId, params: params } })
        )

        return validatedPackagesData.data
      }),
    breakfast: safeProtectedServiceProcedure
      .input(getBreakfastPackageInputSchema)
      .query(async function ({ ctx, input }) {
        const { lang } = ctx

        const apiLang = toApiLang(lang)
        const params = {
          Adults: input.adults,
          EndDate: dt(input.toDate).format("YYYY-MM-DD"),
          StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
          language: apiLang,
        }

        const metricsData = { ...params, hotelId: input.hotelId }
        breakfastPackagesCounter.add(1, metricsData)
        console.info(
          "api.package.breakfast start",
          JSON.stringify({ query: metricsData })
        )

        const apiResponse = await api.get(
          api.endpoints.v1.Package.Breakfast.hotel(input.hotelId),
          {
            cache: undefined,
            headers: {
              Authorization: `Bearer ${ctx.serviceToken}`,
            },
            next: {
              revalidate: 60,
            },
          },
          params
        )

        if (!apiResponse.ok) {
          const text = await apiResponse.text()
          breakfastPackagesFailCounter.add(1, {
            ...metricsData,
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text,
            }),
          })
          console.error(
            "api.hotels.hotelsAvailability error",
            JSON.stringify({
              query: metricsData,
              error: {
                status: apiResponse.status,
                statusText: apiResponse.statusText,
                text,
              },
            })
          )
          return null
        }

        const apiJson = await apiResponse.json()
        const breakfastPackages = breakfastPackagesSchema.safeParse(apiJson)
        if (!breakfastPackages.success) {
          hotelsAvailabilityFailCounter.add(1, {
            ...metricsData,
            error_type: "validation_error",
            error: JSON.stringify(breakfastPackages.error),
          })
          console.error(
            "api.package.breakfast validation error",
            JSON.stringify({
              query: metricsData,
              error: breakfastPackages.error,
            })
          )
          return null
        }

        breakfastPackagesSuccessCounter.add(1, metricsData)
        console.info(
          "api.package.breakfast success",
          JSON.stringify({
            query: metricsData,
          })
        )

        if (ctx.session?.token) {
          const apiUser = await getVerifiedUser({ session: ctx.session })
          if (apiUser && !("error" in apiUser)) {
            const user = parsedUser(apiUser.data, false)
            if (
              user.membership &&
              ["L6", "L7"].includes(user.membership.membershipLevel)
            ) {
              const freeBreakfastPackage = breakfastPackages.data.find(
                (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
              )
              if (freeBreakfastPackage?.localPrice) {
                return [freeBreakfastPackage]
              }
            }
          }
        }

        return breakfastPackages.data.filter(
          (pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
        )
      }),
  }),
})
