import { z } from "zod"

import { Lang } from "@/constants/languages"
import { safeProtectedServiceProcedure } from "@/server/trpc"
import { isDefined } from "@/server/utils"

import { getCacheClient } from "@/services/dataCache"
import { safeTry } from "@/utils/safeTry"

import { getCityPageUrls } from "../contentstack/destinationCityPage/utils"
import { getCountryPageUrls } from "../contentstack/destinationCountryPage/utils"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { getCitiesByCountry, getCountries, getLocations } from "../hotels/utils"
import { filterAndCategorizeAutoComplete } from "./util/filterAndCategorizeAutoComplete"
import { mapLocationToAutoCompleteLocation } from "./util/mapLocationToAutoCompleteLocation"

import { ApiCountry, type Country } from "@/types/enums/country"
import type { AutoCompleteLocation } from "./schema"

const destinationsAutoCompleteInputSchema = z.object({
  query: z.string(),
  selectedHotelId: z.string().optional(),
  selectedCity: z.string().optional(),
  lang: z.nativeEnum(Lang),
  includeTypes: z.array(z.enum(["hotels", "cities", "countries"])),
})

type DestinationsAutoCompleteOutput = {
  hits: {
    hotels: AutoCompleteLocation[]
    cities: AutoCompleteLocation[]
    countries: AutoCompleteLocation[]
  }
  currentSelection: {
    hotel: (AutoCompleteLocation & { type: "hotels" }) | null
    city: (AutoCompleteLocation & { type: "cities" }) | null
  }
}

export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
  .input(destinationsAutoCompleteInputSchema)
  .query(async ({ ctx, input }): Promise<DestinationsAutoCompleteOutput> => {
    const lang = input.lang || ctx.lang
    const locations: AutoCompleteLocation[] =
      await getAutoCompleteDestinationsData({
        lang,
        serviceToken: ctx.serviceToken,
      })

    const hits = filterAndCategorizeAutoComplete({
      locations: locations,
      query: input.query,
      includeTypes: input.includeTypes,
    })

    const selectedHotel = locations.find(
      (location) =>
        location.type === "hotels" && location.id === input.selectedHotelId
    )

    const selectedCity = locations.find(
      (location) =>
        location.type === "cities" &&
        location.cityIdentifier === input.selectedCity
    )

    return {
      hits: hits,
      currentSelection: {
        city: isCity(selectedCity) ? selectedCity : null,
        hotel: isHotel(selectedHotel) ? selectedHotel : null,
      },
    }
  })

function isHotel(
  location: AutoCompleteLocation | null | undefined
): location is AutoCompleteLocation & { type: "hotels" } {
  return !!location && location.type === "hotels"
}

function isCity(
  location: AutoCompleteLocation | null | undefined
): location is AutoCompleteLocation & { type: "cities" } {
  return !!location && location.type === "cities"
}

export async function getAutoCompleteDestinationsData({
  lang,
  serviceToken,
  warmup = false,
}: {
  lang: Lang
  serviceToken: string
  warmup?: boolean
}) {
  const cacheClient = await getCacheClient()
  return await cacheClient.cacheOrGet(
    `autocomplete:destinations:locations:${lang}`,
    async () => {
      const hotelUrlsPromise = safeTry(getHotelPageUrls(lang))
      const cityUrlsPromise = safeTry(getCityPageUrls(lang))
      const countryUrlsPromise = safeTry(getCountryPageUrls(lang))
      const countries = await getCountries({
        lang: lang,
        serviceToken,
      })

      if (!countries) {
        console.error("Unable to fetch countries")
        throw new Error("Unable to fetch countries")
      }

      const countryNames = countries.data.map((country) => country.name)
      const citiesByCountry = await getCitiesByCountry({
        countries: countryNames,
        serviceToken: serviceToken,
        lang,
      })

      const locations = await getLocations({
        lang: lang,
        serviceToken: serviceToken,
        citiesByCountry: citiesByCountry,
      })

      const activeLocations = locations.filter((location) => {
        return (
          location.type === "cities" ||
          (location.type === "hotels" && location.isActive)
        )
      })

      const [hotelUrls, hotelUrlsError] = await hotelUrlsPromise
      const [cityUrls, cityUrlsError] = await cityUrlsPromise
      const [countryUrls, countryUrlsError] = await countryUrlsPromise

      if (
        hotelUrlsError ||
        cityUrlsError ||
        countryUrlsError ||
        !hotelUrls ||
        !cityUrls ||
        !countryUrls
      ) {
        console.error("Unable to fetch location URLs")
        throw new Error("Unable to fetch location URLs")
      }

      const hotelsAndCities = activeLocations
        .map((location) => {
          let url: string | undefined

          if (location.type === "cities") {
            url = cityUrls.find(
              (c) =>
                c.city &&
                location.cityIdentifier &&
                c.city === location.cityIdentifier
            )?.url
          }

          if (location.type === "hotels") {
            url = hotelUrls.find(
              (h) => h.hotelId && location.id && h.hotelId === location.id
            )?.url
          }

          return { ...location, url }
        })
        .map(mapLocationToAutoCompleteLocation)
        .filter(isDefined)

      const countryAutoCompleteLocations = countries.data.map((country) => {
        const url = countryUrls.find(
          (c) =>
            c.country && ApiCountry[lang][c.country as Country] === country.name
        )?.url

        return {
          id: country.id,
          name: country.name,
          type: "countries",
          searchTokens: [country.name],
          destination: "",
          url,
        } satisfies AutoCompleteLocation
      })

      return [...hotelsAndCities, ...countryAutoCompleteLocations]
    },
    "1d",
    { cacheStrategy: warmup ? "fetch-then-cache" : "cache-first" }
  )
}
