import { cache } from "react"

import { env } from "@/env/server"
import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql"
import { GetCampaignPageMetadata } from "@/lib/graphql/Query/CampaignPage/Metadata.graphql"
import { GetCollectionPageMetadata } from "@/lib/graphql/Query/CollectionPage/Metadata.graphql"
import { GetContentPageMetadata } from "@/lib/graphql/Query/ContentPage/Metadata.graphql"
import { GetDestinationCityPageMetadata } from "@/lib/graphql/Query/DestinationCityPage/Metadata.graphql"
import { GetDestinationCountryPageMetadata } from "@/lib/graphql/Query/DestinationCountryPage/Metadata.graphql"
import { GetDestinationOverviewPageMetadata } from "@/lib/graphql/Query/DestinationOverviewPage/Metadata.graphql"
import { GetHotelPageMetadata } from "@/lib/graphql/Query/HotelPage/Metadata.graphql"
import { GetLoyaltyPageMetadata } from "@/lib/graphql/Query/LoyaltyPage/Metadata.graphql"
import { GetStartPageMetadata } from "@/lib/graphql/Query/StartPage/Metadata.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"

import { generateTag } from "@/utils/generateTag"

import { getHotel } from "../../hotels/utils"
import { getUrlsOfAllLanguages } from "../languageSwitcher/utils"
import { getMetadataInput } from "./input"
import { getNonContentstackUrls, metadataSchema } from "./output"
import { affix, getCityData, getCountryData } from "./utils"

import type { Metadata } from "next"

import { PageContentTypeEnum } from "@/types/requests/contentType"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
import type { Lang } from "@/constants/languages"

const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
  query: string,
  { uid, lang }: { uid: string; lang: Lang }
) {
  const getMetadataCounter = createCounter("trpc.contentstack", "metadata.get")
  const metricsGetMetadata = getMetadataCounter.init({ lang, uid })

  metricsGetMetadata.start()

  const response = await request<T>(
    query,
    { locale: lang, uid },
    {
      key: generateTag(lang, uid, affix),
      ttl: "max",
    }
  )

  if (!response.data) {
    const notFoundError = notFound(response)
    metricsGetMetadata.noDataError()
    throw notFoundError
  }

  metricsGetMetadata.success()

  return response.data
})

async function getTransformedMetadata(data: unknown) {
  const transformMetadataCounter = createCounter(
    "trpc.contentstack",
    "metadata.transform"
  )
  const metricsTransformMetadata = transformMetadataCounter.init()

  metricsTransformMetadata.start()

  const validatedMetadata = await metadataSchema.safeParseAsync(data)

  if (!validatedMetadata.success) {
    metricsTransformMetadata.validationError(validatedMetadata.error)
    return null
  }

  metricsTransformMetadata.success()

  return validatedMetadata.data
}

export const metadataQueryRouter = router({
  get: contentStackUidWithServiceProcedure
    .input(getMetadataInput)
    .query(async ({ ctx, input }) => {
      const variables = {
        lang: ctx.lang,
        uid: ctx.uid,
      }

      let urls: LanguageSwitcherData | null = null
      if (
        input.subpage ||
        input.filterFromUrl ||
        !ctx.uid ||
        !ctx.contentType
      ) {
        urls = getNonContentstackUrls(ctx.lang, `${ctx.lang}/${ctx.pathname}`)
      } else {
        urls = await getUrlsOfAllLanguages(ctx.lang, ctx.uid, ctx.contentType)
      }

      let alternates: Metadata["alternates"] = null

      if (urls) {
        const languages: Record<string, string> = {}
        Object.entries(urls)
          .filter(([lang]) => lang !== ctx.lang)
          .forEach(([lang, { url }]) => {
            languages[lang] = url
          })
        const canonical = urls[ctx.lang]?.url
        alternates = {
          canonical,
          languages,
        }
      }

      let metadata: Metadata | null = null

      switch (ctx.contentType) {
        case PageContentTypeEnum.accountPage:
          const accountPageResponse = await fetchMetadata<{
            account_page: RawMetadataSchema
          }>(GetAccountPageMetadata, variables)
          metadata = await getTransformedMetadata(
            accountPageResponse.account_page
          )
          break
        case PageContentTypeEnum.campaignPage:
          const campaignPageResponse = await fetchMetadata<{
            campaign_page: RawMetadataSchema
          }>(GetCampaignPageMetadata, variables)
          metadata = await getTransformedMetadata(
            campaignPageResponse.campaign_page
          )
          break
        case PageContentTypeEnum.collectionPage:
          const collectionPageResponse = await fetchMetadata<{
            collection_page: RawMetadataSchema
          }>(GetCollectionPageMetadata, variables)
          metadata = await getTransformedMetadata(
            collectionPageResponse.collection_page
          )
          break
        case PageContentTypeEnum.contentPage:
          const contentPageResponse = await fetchMetadata<{
            content_page: RawMetadataSchema
          }>(GetContentPageMetadata, variables)
          metadata = await getTransformedMetadata(
            contentPageResponse.content_page
          )
          break
        case PageContentTypeEnum.destinationOverviewPage:
          const destinationOverviewPageResponse = await fetchMetadata<{
            destination_overview_page: RawMetadataSchema
          }>(GetDestinationOverviewPageMetadata, variables)
          metadata = await getTransformedMetadata(
            destinationOverviewPageResponse.destination_overview_page
          )
          break
        case PageContentTypeEnum.destinationCountryPage:
          const destinationCountryPageResponse = await fetchMetadata<{
            destination_country_page: RawMetadataSchema
          }>(GetDestinationCountryPageMetadata, variables)
          const countryData = await getCountryData(
            destinationCountryPageResponse.destination_country_page,
            input,
            ctx.serviceToken,
            ctx.lang
          )
          metadata = await getTransformedMetadata({
            ...destinationCountryPageResponse.destination_country_page,
            destinationData: countryData,
          })
          break
        case PageContentTypeEnum.destinationCityPage:
          const destinationCityPageResponse = await fetchMetadata<{
            destination_city_page: RawMetadataSchema
          }>(GetDestinationCityPageMetadata, variables)
          const cityData = await getCityData(
            destinationCityPageResponse.destination_city_page,
            input,
            ctx.serviceToken,
            ctx.lang
          )
          metadata = await getTransformedMetadata({
            ...destinationCityPageResponse.destination_city_page,
            destinationData: cityData,
          })
          break
        case PageContentTypeEnum.loyaltyPage:
          const loyaltyPageResponse = await fetchMetadata<{
            loyalty_page: RawMetadataSchema
          }>(GetLoyaltyPageMetadata, variables)
          metadata = await getTransformedMetadata(
            loyaltyPageResponse.loyalty_page
          )
          break
        case PageContentTypeEnum.hotelPage:
          const hotelPageResponse = await fetchMetadata<{
            hotel_page: RawMetadataSchema
          }>(GetHotelPageMetadata, variables)
          const hotelPageData = hotelPageResponse.hotel_page
          const hotelData = hotelPageData.hotel_page_id
            ? await getHotel(
                {
                  hotelId: hotelPageData.hotel_page_id,
                  isCardOnlyPayment: false,
                  language: ctx.lang,
                },
                ctx.serviceToken
              )
            : null

          metadata = await getTransformedMetadata({
            ...hotelPageData,
            ...(hotelData
              ? {
                  hotelData: hotelData.hotel,
                  additionalHotelData: hotelData.additionalData,
                  hotelRestaurants: hotelData.restaurants,
                }
              : {}),
            subpageUrl: input.subpage,
          })
          break
        case PageContentTypeEnum.startPage:
          const startPageResponse = await fetchMetadata<{
            start_page: RawMetadataSchema
          }>(GetStartPageMetadata, variables)
          metadata = await getTransformedMetadata(startPageResponse.start_page)
        default:
          break
      }

      if (metadata) {
        if (alternates) {
          // Hiding alternates until all languages are released in production
          if (env.NEW_SITE_LIVE_STATUS === "ALL_LANGUAGES_LIVE") {
            metadata.alternates = alternates
          }
        }
        if (input.noIndex) {
          metadata.robots = {
            index: false,
            follow: false,
          }
        }
      }

      return { metadata, alternates }
    }),
})
