import { z } from "zod"

import { BookingStatusEnum, ChildBedTypeEnum } from "@/constants/booking"

import { calculateRefId } from "@/utils/refId"
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
import {
  nullableStringEmailValidator,
  nullableStringValidator,
} from "@/utils/zod/stringValidator"

import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import { CurrencyEnum } from "@/types/enums/currency"

const guestSchema = z.object({
  email: nullableStringEmailValidator,
  firstName: nullableStringValidator,
  lastName: nullableStringValidator,
  membershipNumber: nullableStringValidator,
  phoneNumber: nullableStringValidator,
  countryCode: nullableStringValidator,
})

export type Guest = z.output<typeof guestSchema>

// MUTATION
export const createBookingSchema = z
  .object({
    data: z.object({
      attributes: z.object({
        reservationStatus: z.string(),
        guest: guestSchema.optional(),
        paymentUrl: z.string().nullable().optional(),
        rooms: z
          .array(
            z.object({
              confirmationNumber: z.string(),
              cancellationNumber: z.string().nullable(),
              priceChangedMetadata: z
                .object({
                  roomPrice: z.number(),
                  totalPrice: z.number(),
                })
                .nullable()
                .optional(),
            })
          )
          .default([]),
        errors: z
          .array(
            z.object({
              confirmationNumber: z.string().nullable().optional(),
              errorCode: z.string(),
              description: z.string().nullable().optional(),
              meta: z
                .record(z.string(), z.union([z.string(), z.number()]))
                .nullable()
                .optional(),
            })
          )
          .default([]),
      }),
      type: z.string(),
      id: z.string(),
      links: z.object({
        self: z.object({
          href: z.string().url(),
          meta: z.object({
            method: z.string(),
          }),
        }),
      }),
    }),
  })
  .transform((d) => ({
    id: d.data.id,
    links: d.data.links,
    type: d.data.type,
    reservationStatus: d.data.attributes.reservationStatus,
    paymentUrl: d.data.attributes.paymentUrl,
    rooms: d.data.attributes.rooms.map((room) => {
      const lastName = d.data.attributes.guest?.lastName ?? ""
      return {
        ...room,
        refId: calculateRefId(room.confirmationNumber, lastName),
      }
    }),
    errors: d.data.attributes.errors,
    guest: d.data.attributes.guest,
  }))

// QUERY
const childBedPreferencesSchema = z.object({
  bedType: z.nativeEnum(ChildBedTypeEnum),
  quantity: z.number().int(),
  code: z.string().nullable().default(""),
})

const priceSchema = z.object({
  currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.Unknown),
  totalPrice: z.number().nullish(),
  totalUnit: z.number().int().nullish(),
  unit: z.number().int().nullish(),
  unitPrice: z.number(),
})

export const packageSchema = z
  .object({
    code: nullableStringValidator,
    comment: z.string().nullish(),
    description: nullableStringValidator,
    price: priceSchema,
    type: z.string().nullish(),
  })
  .transform((packageData) => ({
    code: packageData.code,
    comment: packageData.comment,
    currency: packageData.price.currency,
    description: packageData.description,
    totalPrice: packageData.price.totalPrice ?? 0,
    totalUnit: packageData.price.totalUnit ?? 0,
    type: packageData.type,
    unit: packageData.price.unit ?? 0,
    unitPrice: packageData.price.unitPrice,
  }))

const ancillarySchema = z
  .object({
    comment: z.string().default(""),
    deliveryTime: z.string().default(""),
  })
  .nullable()
  .default({
    comment: "",
    deliveryTime: "",
  })

const rateDefinitionSchema = z.object({
  breakfastIncluded: z.boolean().default(false),
  cancellationRule: z.string().nullable().default(""),
  cancellationText: z.string().nullable().default(""),
  generalTerms: z.array(z.string()).default([]),
  isMemberRate: z.boolean().default(false),
  mustBeGuaranteed: z.boolean().default(false),
  rateCode: z.string().default(""),
  title: z.string().nullable().default(""),
})

export const linkedReservationSchema = z.object({
  confirmationNumber: z.string().default(""),
  hotelId: z.string().default(""),
  checkinDate: z.string(),
  checkoutDate: z.string(),
  cancellationNumber: nullableStringValidator,
  roomTypeCode: z.string().default(""),
  adults: z.number().int(),
  children: z.number().int(),
  profileId: z.string().default(""),
})

const linksSchema = z.object({
  addAncillary: z
    .object({
      href: z.string(),
      meta: z.object({
        method: z.string(),
      }),
    })
    .nullable(),
  cancel: z
    .object({
      href: z.string(),
      meta: z.object({
        method: z.string(),
      }),
    })
    .nullable(),
  guarantee: z
    .object({
      href: z.string(),
      meta: z.object({
        method: z.string(),
      }),
    })
    .nullable(),
  modify: z
    .object({
      href: z.string(),
      meta: z.object({
        method: z.string(),
      }),
    })
    .nullable(),
  self: z
    .object({
      href: z.string(),
      meta: z.object({
        method: z.string(),
      }),
    })
    .nullable(),
})

export const bookingConfirmationSchema = z
  .object({
    data: z.object({
      attributes: z.object({
        adults: z.number().int(),
        ancillary: ancillarySchema,
        cancelationNumber: z.string().nullable().default(""),
        checkInDate: z.date({ coerce: true }),
        checkOutDate: z.date({ coerce: true }),
        childBedPreferences: z.array(childBedPreferencesSchema).default([]),
        childrenAges: z.array(z.number().int()).default([]),
        canChangeDate: z.boolean(),
        bookingCode: z.string().nullable(),
        cheques: z.number(),
        vouchers: z.number(),
        guaranteeInfo: z
          .object({
            maskedCard: z.string(),
            cardType: z.string(),
            paymentMethod: z.string(),
            paymentMethodDescription: z.string(),
          })
          .nullish(),
        computedReservationStatus: z.string().nullable().default(""),
        confirmationNumber: nullableStringValidator,
        createDateTime: z.date({ coerce: true }),
        currencyCode: z.nativeEnum(CurrencyEnum),
        guest: guestSchema,
        linkedReservations: nullableArrayObjectValidator(
          linkedReservationSchema
        ),
        hotelId: z.string(),
        mainRoom: z.boolean(),
        multiRoom: z.boolean(),
        packages: z.array(packageSchema).default([]),
        rateDefinition: rateDefinitionSchema,
        reservationStatus: z.string().nullable().default(""),
        roomPoints: z.number(),
        roomPrice: z.number(),
        roomTypeCode: z.string().default(""),
        totalPoints: z.number(),
        totalPrice: z.number(),
        totalPriceExVat: z.number(),
        vatAmount: z.number(),
        vatPercentage: z.number(),
      }),
      id: z.string(),
      type: z.literal("booking"),
      links: linksSchema,
    }),
  })
  .transform(({ data }) => ({
    ...data.attributes,
    refId: calculateRefId(
      data.attributes.confirmationNumber,
      data.attributes.guest.lastName
    ),
    linkedReservations: data.attributes.linkedReservations.map(
      (linkedReservation) => {
        /**
         * We lazy load linked reservations in the client.
         * The problem is that we need to load the reservation in order to
         * calculate the refId for the reservation as the refId uses the guest's
         * lastname in it. Ideally we should pass a promise to the React
         * component that uses `use()` to resolve it. But right now we use tRPC
         * in the client. That tRPC endpoint only uses the confirmationNumber
         * from the refId. So that means we can pass whatever as the lastname
         * here, because it is actually never read. We should change this ASAP.
         */
        return {
          ...linkedReservation,
          refId: calculateRefId(
            linkedReservation.confirmationNumber,
            "" // TODO: Empty lastname here, see comment above
          ),
        }
      }
    ),
    packages: data.attributes.packages.filter((p) => p.type !== "Ancillary"),
    ancillaries: data.attributes.packages.filter((p) => p.type === "Ancillary"),
    extraBedTypes: data.attributes.childBedPreferences,
    showAncillaries:
      !!(
        data.links.addAncillary ||
        data.attributes.packages.some(
          (p) =>
            p.type === "Ancillary" ||
            p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
        )
      ) && data.attributes.reservationStatus !== BookingStatusEnum.Cancelled,
    isCancelable: !!data.links.cancel,
    isModifiable: !!data.links.modify,
    canModifyAncillaries: !!data.links.addAncillary,
    // Typo from API
    cancellationNumber: data.attributes.cancelationNumber,
  }))
