import deepmerge from "deepmerge"
import { unstable_cache } from "next/cache"

import * as api from "@/lib/api"

import {
  apiCitiesByCountrySchema,
  apiCitySchema,
  apiCountriesSchema,
  apiLocationsSchema,
  type CitiesGroupedByCountry,
  type Countries,
} from "./output"

import type { RequestOptionsWithOutBody } from "@/types/fetch"
import {
  PointOfInterestCategoryNameEnum,
  PointOfInterestGroupEnum,
} from "@/types/hotel"
import { HotelLocation } from "@/types/trpc/routers/hotel/locations"
import type { Lang } from "@/constants/languages"
import type { Endpoint } from "@/lib/api/endpoints"

export function getPoiGroupByCategoryName(
  category: PointOfInterestCategoryNameEnum
) {
  switch (category) {
    case PointOfInterestCategoryNameEnum.AIRPORT:
    case PointOfInterestCategoryNameEnum.BUS_TERMINAL:
    case PointOfInterestCategoryNameEnum.TRANSPORTATIONS:
      return PointOfInterestGroupEnum.PUBLIC_TRANSPORT
    case PointOfInterestCategoryNameEnum.AMUSEMENT_PARK:
    case PointOfInterestCategoryNameEnum.MUSEUM:
    case PointOfInterestCategoryNameEnum.SPORTS:
    case PointOfInterestCategoryNameEnum.THEATRE:
    case PointOfInterestCategoryNameEnum.TOURIST:
    case PointOfInterestCategoryNameEnum.ZOO:
      return PointOfInterestGroupEnum.ATTRACTIONS
    case PointOfInterestCategoryNameEnum.NEARBY_COMPANIES:
    case PointOfInterestCategoryNameEnum.FAIR:
      return PointOfInterestGroupEnum.BUSINESS
    case PointOfInterestCategoryNameEnum.PARKING_GARAGE:
      return PointOfInterestGroupEnum.PARKING
    case PointOfInterestCategoryNameEnum.SHOPPING:
    case PointOfInterestCategoryNameEnum.RESTAURANT:
      return PointOfInterestGroupEnum.SHOPPING_DINING
    case PointOfInterestCategoryNameEnum.HOSPITAL:
    default:
      return PointOfInterestGroupEnum.LOCATION
  }
}

export const locationsAffix = "locations"

export const TWENTYFOUR_HOURS = 60 * 60 * 24
export async function getCity(
  cityUrl: string,
  options: RequestOptionsWithOutBody,
  lang: Lang,
  relationshipCity: HotelLocation["relationships"]["city"]
) {
  return unstable_cache(
    async function (locationCityUrl: string) {
      const url = new URL(locationCityUrl)
      const cityResponse = await api.get(
        url.pathname as Endpoint,
        options,
        url.searchParams
      )

      if (!cityResponse.ok) {
        return null
      }

      const cityJson = await cityResponse.json()
      const city = apiCitySchema.safeParse(cityJson)
      if (!city.success) {
        console.info(`Validation of city failed`)
        console.info(`cityUrl: ${locationCityUrl}`)
        console.error(city.error)
        return null
      }

      return city.data
    },
    [cityUrl, `${lang}:${relationshipCity}`],
    { revalidate: TWENTYFOUR_HOURS }
  )(cityUrl)
}

export async function getCountries(
  options: RequestOptionsWithOutBody,
  params: URLSearchParams,
  lang: Lang
) {
  return unstable_cache(
    async function (searchParams) {
      const countryResponse = await api.get(
        api.endpoints.v1.Hotel.countries,
        options,
        searchParams
      )

      if (!countryResponse.ok) {
        return null
      }

      const countriesJson = await countryResponse.json()
      const countries = apiCountriesSchema.safeParse(countriesJson)
      if (!countries.success) {
        console.info(`Validation for countries failed`)
        console.error(countries.error)
        return null
      }

      return countries.data
    },
    [`${lang}:${locationsAffix}:countries`, params.toString()],
    { revalidate: TWENTYFOUR_HOURS }
  )(params)
}

export async function getCitiesByCountry(
  countries: Countries,
  options: RequestOptionsWithOutBody,
  params: URLSearchParams,
  lang: Lang
) {
  return unstable_cache(
    async function (
      searchParams: URLSearchParams,
      searchedCountries: Countries
    ) {
      const citiesGroupedByCountry: CitiesGroupedByCountry = {}

      await Promise.all(
        searchedCountries.data.map(async (country) => {
          const countryResponse = await api.get(
            api.endpoints.v1.Hotel.Cities.country(country.name),
            options,
            searchParams
          )

          if (!countryResponse.ok) {
            return null
          }

          const countryJson = await countryResponse.json()
          const citiesByCountry =
            apiCitiesByCountrySchema.safeParse(countryJson)
          if (!citiesByCountry.success) {
            console.info(`Failed to validate Cities by Country payload`)
            console.error(citiesByCountry.error)
            return null
          }

          citiesGroupedByCountry[country.name] = citiesByCountry.data.data
          return true
        })
      )

      return citiesGroupedByCountry
    },
    [
      `${lang}:${locationsAffix}:cities-by-country`,
      params.toString(),
      JSON.stringify(countries),
    ],
    { revalidate: TWENTYFOUR_HOURS }
  )(params, countries)
}

export async function getLocations(
  lang: Lang,
  options: RequestOptionsWithOutBody,
  params: URLSearchParams,
  citiesByCountry: CitiesGroupedByCountry | null
) {
  return unstable_cache(
    async function (
      searchParams: URLSearchParams,
      groupedCitiesByCountry: CitiesGroupedByCountry | null
    ) {
      const apiResponse = await api.get(
        api.endpoints.v1.Hotel.locations,
        options,
        searchParams
      )

      if (!apiResponse.ok) {
        if (apiResponse.status === 401) {
          return { error: true, cause: "unauthorized" } as const
        } else if (apiResponse.status === 403) {
          return { error: true, cause: "forbidden" } as const
        }
        return null
      }

      const apiJson = await apiResponse.json()
      const verifiedLocations = apiLocationsSchema.safeParse(apiJson)
      if (!verifiedLocations.success) {
        console.info(`Locations Verification Failed`)
        console.error(verifiedLocations.error)
        return null
      }

      return await Promise.all(
        verifiedLocations.data.data.map(async (location) => {
          if (location.type === "cities") {
            if (groupedCitiesByCountry) {
              const country = Object.keys(groupedCitiesByCountry).find(
                (country) => {
                  if (
                    groupedCitiesByCountry[country].find(
                      (loc) => loc.name === location.name
                    )
                  ) {
                    return true
                  }
                  return false
                }
              )
              if (country) {
                return {
                  ...location,
                  country,
                }
              } else {
                console.info(
                  `Location cannot be found in any of the countries cities`
                )
                console.info(location)
              }
            }
          } else if (location.type === "hotels") {
            if (location.relationships.city?.url) {
              const city = await getCity(
                location.relationships.city.url,
                options,
                lang,
                location.relationships.city
              )
              if (city) {
                return deepmerge(location, {
                  relationships: {
                    city,
                  },
                })
              }
            }
          }

          return location
        })
      )
    },
    [
      `${lang}:${locationsAffix}`,
      params.toString(),
      JSON.stringify(citiesByCountry),
    ],
    { revalidate: TWENTYFOUR_HOURS }
  )(params, citiesByCountry)
}
