import { z } from "zod"

import { ChildBedTypeEnum } from "@/constants/booking"
import { dt } from "@/lib/dt"
import { toLang } from "@/server/utils"

import { imageMetaDataSchema, imageSizesSchema } from "./schemas/image"
import { roomSchema } from "./schemas/room"
import { getPoiGroupByCategoryName } from "./utils"

import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { AlertTypeEnum } from "@/types/enums/alert"
import { CurrencyEnum } from "@/types/enums/currency"
import { FacilityEnum } from "@/types/enums/facilities"
import { PackageTypeEnum } from "@/types/enums/packages"
import { PointOfInterestCategoryNameEnum } from "@/types/hotel"

const ratingsSchema = z
  .object({
    tripAdvisor: z.object({
      numberOfReviews: z.number(),
      rating: z.number(),
      ratingImageUrl: z.string(),
      webUrl: z.string(),
      awards: z.array(
        z.object({
          displayName: z.string(),
          images: z.object({
            small: z.string(),
            medium: z.string(),
            large: z.string(),
          }),
        })
      ),
      reviews: z
        .object({
          widgetHtmlTagId: z.string(),
          widgetScriptEmbedUrlIframe: z.string(),
          widgetScriptEmbedUrlJavaScript: z.string(),
        })
        .optional(),
    }),
  })
  .optional()

const addressSchema = z.object({
  streetAddress: z.string(),
  city: z.string(),
  zipCode: z.string(),
  country: z.string(),
})

const contactInformationSchema = z.object({
  phoneNumber: z.string(),
  faxNumber: z.string().optional(),
  email: z.string(),
  websiteUrl: z.string(),
})

const checkinSchema = z.object({
  checkInTime: z.string(),
  checkOutTime: z.string(),
  onlineCheckOutAvailableFrom: z.string().nullable().optional(),
  onlineCheckout: z.boolean(),
})

const ecoLabelsSchema = z.object({
  euEcoLabel: z.boolean(),
  greenGlobeLabel: z.boolean(),
  nordicEcoLabel: z.boolean(),
  svanenEcoLabelCertificateNumber: z.string().optional(),
})

const interiorSchema = z.object({
  numberOfBeds: z.number(),
  numberOfCribs: z.number(),
  numberOfFloors: z.number(),
  numberOfRooms: z.object({
    connected: z.number(),
    forAllergics: z.number().optional(),
    forDisabled: z.number(),
    nonSmoking: z.number(),
    pet: z.number(),
    withExtraBeds: z.number(),
    total: z.number(),
  }),
})

const receptionHoursSchema = z.object({
  alwaysOpen: z.boolean(),
  isClosed: z.boolean(),
  openingTime: z.string().optional(),
  closingTime: z.string().optional(),
})

const locationSchema = z.object({
  distanceToCentre: z.number(),
  latitude: z.number(),
  longitude: z.number(),
})

const hotelContentSchema = z.object({
  images: z.object({
    metaData: imageMetaDataSchema,
    imageSizes: imageSizesSchema,
  }),
  texts: z.object({
    facilityInformation: z.string(),
    surroundingInformation: z.string(),
    descriptions: z.object({
      short: z.string(),
      medium: z.string(),
    }),
  }),
  restaurantsOverviewPage: z.object({
    restaurantsOverviewPageLinkText: z.string(),
    restaurantsOverviewPageLink: z.string(),
    restaurantsContentDescriptionShort: z.string(),
    restaurantsContentDescriptionMedium: z.string(),
  }),
})

const detailedFacilitySchema = z.object({
  id: z.nativeEnum(FacilityEnum),
  name: z.string(),
  public: z.boolean(),
  sortOrder: z.number(),
  filter: z.string().optional(),
})

export const facilitySchema = z.object({
  headingText: z.string(),
  heroImages: z.array(
    z.object({
      metaData: imageMetaDataSchema,
      imageSizes: imageSizesSchema,
    })
  ),
})

export const imageSchema = z.object({
  metaData: imageMetaDataSchema,
  imageSizes: imageSizesSchema,
})

export const gallerySchema = z.object({
  heroImages: z.array(imageSchema),
  smallerImages: z.array(imageSchema),
})

const healthFacilitySchema = z.object({
  type: z.string(),
  content: z.object({
    images: z.array(imageSchema),
    texts: z.object({
      facilityInformation: z.string().optional(),
      surroundingInformation: z.string().optional(),
      descriptions: z.object({
        short: z.string(),
        medium: z.string(),
      }),
    }),
  }),
  openingDetails: z.object({
    useManualOpeningHours: z.boolean(),
    manualOpeningHours: z.string().optional(),
    openingHours: z.object({
      ordinary: z.object({
        alwaysOpen: z.boolean(),
        isClosed: z.boolean(),
        openingTime: z.string().optional(),
        closingTime: z.string().optional(),
        sortOrder: z.number().optional(),
      }),
      weekends: z.object({
        alwaysOpen: z.boolean(),
        isClosed: z.boolean(),
        openingTime: z.string().optional(),
        closingTime: z.string().optional(),
        sortOrder: z.number().optional(),
      }),
    }),
  }),
  details: z.array(
    z.object({
      name: z.string(),
      type: z.string(),
      value: z.string().optional(),
    })
  ),
})

const rewardNightSchema = z.object({
  points: z.number(),
  campaign: z.object({
    start: z.string(),
    end: z.string(),
    points: z.number(),
  }),
})

const poiCategoryNames = z.nativeEnum(PointOfInterestCategoryNameEnum)

export const pointOfInterestSchema = z
  .object({
    name: z.string(),
    distance: z.number(),
    category: z.object({
      name: poiCategoryNames,
      group: z.string(),
    }),
    location: locationSchema,
    isHighlighted: z.boolean(),
  })
  .transform((poi) => ({
    name: poi.name,
    distance: poi.distance,
    categoryName: poi.category.name,
    group: getPoiGroupByCategoryName(poi.category.name),
    coordinates: {
      lat: poi.location.latitude,
      lng: poi.location.longitude,
    },
  }))

const parkingPricingSchema = z.object({
  freeParking: z.boolean(),
  paymentType: z.string().optional(),
  localCurrency: z.object({
    currency: z.string().optional(),
    range: z.object({
      min: z.number().optional(),
      max: z.number().optional(),
    }),
    ordinary: z
      .array(
        z.object({
          period: z.string().optional(),
          amount: z.number().optional(),
          startTime: z.string().optional(),
          endTime: z.string().optional(),
        })
      )
      .optional(),
    weekend: z
      .array(
        z.object({
          period: z.string().optional(),
          amount: z.number().optional(),
          startTime: z.string().optional(),
          endTime: z.string().optional(),
        })
      )
      .optional(),
  }),
  requestedCurrency: z
    .object({
      currency: z.string().optional(),
      range: z
        .object({
          min: z.number().optional(),
          max: z.number().optional(),
        })
        .optional(),
      ordinary: z
        .array(
          z.object({
            period: z.string().optional(),
            amount: z.number().optional(),
            startTime: z.string().optional(),
            endTime: z.string().optional(),
          })
        )
        .optional(),
      weekend: z
        .array(
          z.object({
            period: z.string().optional(),
            amount: z.number().optional(),
            startTime: z.string().optional(),
            endTime: z.string().optional(),
          })
        )
        .optional(),
    })
    .optional(),
})

export const parkingSchema = z.object({
  type: z.string().optional(),
  name: z.string().optional(),
  address: z.string().optional(),
  numberOfParkingSpots: z.number().optional(),
  numberOfChargingSpaces: z.number().optional(),
  distanceToHotel: z.number().optional(),
  canMakeReservation: z.boolean(),
  pricing: parkingPricingSchema,
})

const specialNeedSchema = z.object({
  name: z.string(),
  details: z.string(),
})

const specialNeedGroupSchema = z.object({
  name: z.string(),
  specialNeeds: z.array(specialNeedSchema),
})

const socialMediaSchema = z.object({
  instagram: z.string().optional(),
  facebook: z.string().optional(),
})

const specialAlertSchema = z.object({
  type: z.string(),
  title: z.string().optional(),
  description: z.string().optional(),
  displayInBookingFlow: z.boolean(),
  startDate: z.string().optional(),
  endDate: z.string().optional(),
})

const specialAlertsSchema = z
  .array(specialAlertSchema)
  .transform((data) => {
    const now = dt().utc().format("YYYY-MM-DD")
    const filteredAlerts = data.filter((alert) => {
      const shouldShowNow =
        alert.startDate && alert.endDate
          ? alert.startDate <= now && alert.endDate >= now
          : true
      const hasText = alert.description || alert.title
      return shouldShowNow && hasText
    })
    return filteredAlerts.map((alert, idx) => ({
      id: `alert-${alert.type}-${idx}`,
      type: AlertTypeEnum.Info,
      heading: alert.title || null,
      text: alert.description || null,
    }))
  })
  .default([])

const relationshipsSchema = z.object({
  restaurants: z.object({
    links: z.object({
      related: z.string(),
    }),
  }),
  nearbyHotels: z.object({
    links: z.object({
      related: z.string(),
    }),
  }),
  roomCategories: z.object({
    links: z.object({
      related: z.string(),
    }),
  }),
  meetingRooms: z.object({
    links: z.object({
      related: z.string(),
    }),
  }),
})

const merchantInformationSchema = z.object({
  webMerchantId: z.string(),
  cards: z.record(z.string(), z.boolean()).transform((val) => {
    return Object.entries(val)
      .filter(([_, enabled]) => enabled)
      .map(([key]) => key)
  }),
  alternatePaymentOptions: z
    .record(z.string(), z.boolean())
    .transform((val) => {
      return Object.entries(val)
        .filter(([_, enabled]) => enabled)
        .map(([key]) => key)
    }),
})

const hotelFacilityDetailSchema = z
  .object({
    description: z.string(),
    heading: z.string(),
  })
  .optional()

/** Possibly more values */
const hotelFacilityDetailsSchema = z.object({
  breakfast: hotelFacilityDetailSchema,
  checkout: hotelFacilityDetailSchema,
  gym: hotelFacilityDetailSchema,
  internet: hotelFacilityDetailSchema,
  laundry: hotelFacilityDetailSchema,
  luggage: hotelFacilityDetailSchema,
  shop: hotelFacilityDetailSchema,
  telephone: hotelFacilityDetailSchema,
})

const hotelInformationSchema = z
  .object({
    description: z.string(),
    heading: z.string(),
    link: z.string().optional(),
  })
  .optional()

const hotelInformationsSchema = z.object({
  accessibility: hotelInformationSchema,
  safety: hotelInformationSchema,
  sustainability: hotelInformationSchema,
})

const hotelFactsSchema = z.object({
  checkin: checkinSchema,
  ecoLabels: ecoLabelsSchema,
  hotelFacilityDetail: hotelFacilityDetailsSchema.default({}),
  hotelInformation: hotelInformationsSchema.default({}),
  interior: interiorSchema,
  receptionHours: receptionHoursSchema,
  yearBuilt: z.string(),
})

// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
export const getHotelDataSchema = z.object({
  data: z.object({
    id: z.string(),
    type: z.literal("hotels"), // No enum here but the standard return appears to be "hotels".
    language: z.string().transform((val) => {
      const lang = toLang(val)
      if (!lang) {
        throw new Error("Invalid language")
      }
      return lang
    }),
    attributes: z.object({
      accessibilityElevatorPitchText: z.string().optional(),
      address: addressSchema,
      cityId: z.string(),
      cityName: z.string(),
      conferencesAndMeetings: facilitySchema.optional(),
      contactInformation: contactInformationSchema,
      detailedFacilities: z
        .array(detailedFacilitySchema)
        .transform((facilities) =>
          facilities.sort((a, b) => b.sortOrder - a.sortOrder)
        ),
      gallery: gallerySchema.optional(),
      galleryImages: z.array(imageSchema).optional(),
      healthAndWellness: facilitySchema.optional(),
      healthFacilities: z.array(healthFacilitySchema),
      hotelContent: hotelContentSchema,
      hotelFacts: hotelFactsSchema,
      hotelRoomElevatorPitchText: z.string().optional(),
      hotelType: z.string().optional(),
      isActive: z.boolean(),
      isPublished: z.boolean(),
      keywords: z.array(z.string()),
      location: locationSchema,
      merchantInformationData: merchantInformationSchema,
      name: z.string(),
      operaId: z.string(),
      parking: z.array(parkingSchema),
      pointsOfInterest: z
        .array(pointOfInterestSchema)
        .transform((pois) => pois.sort((a, b) => a.distance - b.distance)),
      ratings: ratingsSchema,
      rewardNight: rewardNightSchema,
      restaurantImages: facilitySchema.optional(),
      socialMedia: socialMediaSchema,
      specialAlerts: specialAlertsSchema,
      specialNeedGroups: z.array(specialNeedGroupSchema),
    }),
    relationships: relationshipsSchema,
  }),
  // NOTE: We can pass an "include" param to the hotel API to retrieve
  // additional data for an individual hotel.
  included: z.array(roomSchema).optional(),
})

export const childrenSchema = z.object({
  age: z.number(),
  bedType: z.nativeEnum(ChildBedTypeEnum),
})

const occupancySchema = z.object({
  adults: z.number(),
  children: z.array(childrenSchema),
})

const bestPricePerStaySchema = z.object({
  currency: z.string(),
  // TODO: remove optional when API is ready
  regularAmount: z.string().optional(),
  // TODO: remove optional when API is ready
  memberAmount: z.string().optional(),
})

const bestPricePerNightSchema = z.object({
  currency: z.string(),
  // TODO: remove optional when API is ready
  regularAmount: z.string().optional(),
  // TODO: remove optional when API is ready
  memberAmount: z.string().optional(),
})

const linksSchema = z.object({
  links: z.array(
    z.object({
      url: z.string().url(),
      type: z.string(),
    })
  ),
})

const hotelsAvailabilitySchema = z.object({
  data: z.array(
    z.object({
      attributes: z.object({
        checkInDate: z.string(),
        checkOutDate: z.string(),
        occupancy: occupancySchema.optional(),
        status: z.string(),
        hotelId: z.number(),
        ratePlanSet: z.string().optional(),
        bestPricePerStay: bestPricePerStaySchema.optional(),
        bestPricePerNight: bestPricePerNightSchema.optional(),
      }),
      relationships: linksSchema.optional(),
      type: z.string().optional(),
    })
  ),
})

export const getHotelsAvailabilitySchema = hotelsAvailabilitySchema
export type HotelsAvailability = z.infer<typeof hotelsAvailabilitySchema>
export type HotelsAvailabilityPrices =
  HotelsAvailability["data"][number]["attributes"]["bestPricePerNight"]

export const priceSchema = z.object({
  pricePerNight: z.coerce.number(),
  pricePerStay: z.coerce.number(),
  currency: z.string(),
})

export const productTypePriceSchema = z.object({
  rateCode: z.string(),
  rateType: z.string().optional(),
  localPrice: priceSchema,
  requestedPrice: priceSchema,
})

const productSchema = z.object({
  productType: z.object({
    public: productTypePriceSchema,
    member: productTypePriceSchema.optional(),
  }),
})

const roomConfigurationSchema = z.object({
  status: z.string(),
  roomTypeCode: z.string(),
  roomType: z.string(),
  roomsLeft: z.number(),
  features: z.array(
    z.object({
      inventory: z.number(),
      code: z.enum([
        RoomPackageCodeEnum.PET_ROOM,
        RoomPackageCodeEnum.ALLERGY_ROOM,
        RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
      ]),
    })
  ),
  products: z.array(productSchema),
})

const rateDefinitionSchema = z.object({
  title: z.string(),
  breakfastIncluded: z.boolean(),
  rateType: z.string().optional(),
  rateCode: z.string(),
  generalTerms: z.array(z.string()),
  cancellationRule: z.string(),
  cancellationText: z.string(),
  mustBeGuaranteed: z.boolean(),
})

const roomsAvailabilitySchema = z
  .object({
    data: z.object({
      attributes: z.object({
        checkInDate: z.string(),
        checkOutDate: z.string(),
        occupancy: occupancySchema.optional(),
        hotelId: z.number(),
        roomConfigurations: z.array(roomConfigurationSchema),
        rateDefinitions: z.array(rateDefinitionSchema),
        mustBeGuaranteed: z.boolean().optional(),
      }),
      relationships: linksSchema.optional(),
      type: z.string().optional(),
    }),
  })
  .transform((o) => o.data.attributes)

export const getRoomsAvailabilitySchema = roomsAvailabilitySchema
export type RoomsAvailability = z.infer<typeof roomsAvailabilitySchema>
export type RoomConfiguration = z.infer<typeof roomConfigurationSchema>
export type Product = z.infer<typeof productSchema>
export type RateDefinition = z.infer<typeof rateDefinitionSchema>

const flexibilityPrice = z.object({
  standard: z.number(),
  member: z.number(),
})

const rate = z.object({
  id: z.number(),
  name: z.string(),
  description: z.string(),
  size: z.string(),
  imageSrc: z.string(),
  breakfastIncluded: z.boolean(),
  prices: z.object({
    currency: z.string(),
    nonRefundable: flexibilityPrice,
    freeRebooking: flexibilityPrice,
    freeCancellation: flexibilityPrice,
  }),
})

export const getRatesSchema = z.array(rate)
export type Rate = z.infer<typeof rate>

const hotelFilter = z.object({
  roomFacilities: z.array(z.string()),
  hotelFacilities: z.array(z.string()),
  hotelSurroundings: z.array(z.string()),
})

export const getFiltersSchema = hotelFilter
export type HotelFilter = z.infer<typeof hotelFilter>

export const apiCitiesByCountrySchema = z.object({
  data: z.array(
    z
      .object({
        attributes: z.object({
          cityIdentifier: z.string().optional(),
          name: z.string(),
          keywords: z.array(z.string()).optional(),
          timeZoneId: z.string().optional(),
          ianaTimeZoneId: z.string().optional(),
          isPublished: z.boolean().optional().default(false),
        }),
        id: z.string(),
        type: z.literal("cities"),
      })
      .transform((data) => {
        return {
          ...data.attributes,
          id: data.id,
          type: data.type,
        }
      })
  ),
})

export interface CitiesByCountry
  extends z.output<typeof apiCitiesByCountrySchema> {}
export type CitiesGroupedByCountry = Record<string, CitiesByCountry["data"]>

export const apiCountriesSchema = z.object({
  data: z
    .array(
      z.object({
        attributes: z.object({
          currency: z.string().optional(),
          name: z.string(),
        }),
        hotelInformationSystemId: z.number().optional(),
        id: z.string().optional().default(""),
        language: z.string().optional(),
        type: z.literal("countries"),
      })
    )
    .transform((data) => {
      return data.map((country) => {
        return {
          ...country.attributes,
          hotelInformationSystemId: country.hotelInformationSystemId,
          id: country.id,
          language: country.language,
          type: country.type,
        }
      })
    }),
})

export interface Countries extends z.output<typeof apiCountriesSchema> {}

export const apiLocationCitySchema = z.object({
  attributes: z.object({
    cityIdentifier: z.string().optional(),
    keyWords: z.array(z.string()).optional(),
    name: z.string().optional().default(""),
  }),
  country: z.string().optional().default(""),
  id: z.string().optional().default(""),
  type: z.literal("cities"),
})

export const apiCitySchema = z
  .object({
    data: z.array(
      z.object({
        attributes: z.object({
          cityIdentifier: z.string().optional(),
          name: z.string(),
          keywords: z.array(z.string()),
          timeZoneId: z.string().optional(),
          ianaTimeZoneId: z.string().optional(),
          isPublished: z.boolean().optional().default(false),
        }),
        id: z.string().optional(),
        type: z.literal("cities"),
      })
    ),
  })
  .transform(({ data }) => {
    if (data.length) {
      const city = data[0]
      return {
        ...city.attributes,
        id: city.id,
        type: city.type,
      }
    }
    return null
  })

export const apiLocationHotelSchema = z.object({
  attributes: z.object({
    distanceToCentre: z.number().optional(),
    images: z
      .object({
        large: z.string().optional(),
        medium: z.string().optional(),
        small: z.string().optional(),
        tiny: z.string().optional(),
      })
      .optional(),
    keyWords: z.array(z.string()).optional(),
    name: z.string().optional().default(""),
    operaId: z.string().optional(),
  }),
  id: z.string().optional().default(""),
  relationships: z
    .object({
      city: z
        .object({
          links: z
            .object({
              related: z.string().optional(),
            })
            .optional(),
        })
        .optional(),
    })
    .optional(),
  type: z.literal("hotels"),
})

export const apiLocationsSchema = z.object({
  data: z
    .array(
      z
        .discriminatedUnion("type", [
          apiLocationCitySchema,
          apiLocationHotelSchema,
        ])
        .transform((location) => {
          if (location.type === "cities") {
            return {
              ...location.attributes,
              country: location?.country ?? "",
              id: location.id,
              type: location.type,
            }
          }
          return {
            ...location.attributes,
            id: location.id,
            relationships: {
              city: {
                cityIdentifier: "",
                ianaTimeZoneId: "",
                id: "",
                isPublished: false,
                keywords: [],
                name: "",
                timeZoneId: "",
                type: "cities",
                url: location?.relationships?.city?.links?.related ?? "",
              },
            },
            type: location.type,
          }
        })
    )
    .transform((data) =>
      data
        .filter((node) => !!node)
        .sort((a, b) => {
          if (a.type === b.type) {
            return a.name.localeCompare(b.name)
          } else {
            return a.type === "cities" ? -1 : 1
          }
        })
    ),
})

export const packagePriceSchema = z.object({
  currency: z.nativeEnum(CurrencyEnum),
  price: z.string(),
  totalPrice: z.string(),
})

export const breakfastPackageSchema = z.object({
  code: z.string(),
  description: z.string(),
  localPrice: packagePriceSchema,
  requestedPrice: packagePriceSchema,
  packageType: z.literal(PackageTypeEnum.BreakfastAdult),
})

export const breakfastPackagesSchema = z
  .object({
    data: z.object({
      attributes: z.object({
        hotelId: z.number(),
        packages: z.array(breakfastPackageSchema),
      }),
      type: z.literal("breakfastpackage"),
    }),
  })
  .transform(({ data }) =>
    data.attributes.packages.filter((pkg) => pkg.code.match(/^(BRF\d+)$/gm))
  )

export const packagesSchema = z.object({
  code: z.nativeEnum(RoomPackageCodeEnum),
  itemCode: z.string().optional(),
  description: z.string(),
  localPrice: packagePriceSchema,
  requestedPrice: packagePriceSchema,
  inventories: z.array(
    z.object({
      date: z.string(),
      total: z.number(),
      available: z.number(),
    })
  ),
})

export const getRoomPackagesSchema = z
  .object({
    data: z.object({
      attributes: z.object({
        hotelId: z.number(),
        packages: z.array(packagesSchema).optional().default([]),
      }),
      relationships: z
        .object({
          links: z.array(
            z.object({
              url: z.string(),
              type: z.string(),
            })
          ),
        })
        .optional(),
      type: z.string(),
    }),
  })
  .transform((data) => data.data.attributes.packages)
