import { z } from "zod"

import { countriesMap } from "@/constants/countries"

import { getFriendsMembership } from "@/utils/user"

import { imageSchema } from "../hotels/schemas/image"

const scandicFriendsTier = z.enum(["L1", "L2", "L3", "L4", "L5", "L6", "L7"])
const sasEurobonusTier = z.enum(["EBB", "EBS", "EBG", "EBD", "EBP"])

const commonMembershipSchema = z.object({
  membershipNumber: z.string(),
  tierExpires: z.string().nullish().default(null),
  memberSince: z.string().nullish(),
})

// This prevents validation errors if the API returns an unhandled membership type
const otherMembershipSchema = z
  .object({
    // This ensures that `type` won't widen into "string", losing the literal types, when used in a union
    type: z.string().refine((val): val is string & {} => true),
  })
  .merge(commonMembershipSchema)

export const sasMembershipSchema = z
  .object({
    type: z.literal("SAS_EB"),
    tier: sasEurobonusTier,
    nextTier: sasEurobonusTier.nullish(),
    spendablePoints: z.number().nullish(),
    boostedByScandic: z.boolean().nullish(),
    boostedTier: sasEurobonusTier.nullish(),
    boostedTierExpires: z.string().nullish().default(null),
  })
  .merge(commonMembershipSchema)
  .transform((response) => {
    return {
      ...response,
      tierExpires:
        // SAS API returns 1900-01-01 for non-expiring tiers
        response.tierExpires === "1900-01-01" ? null : response.tierExpires,
    }
  })

export const friendsMembershipSchema = z
  .object({
    type: z.literal("SCANDIC_NATIVE"),
    tier: scandicFriendsTier,
    nextTier: scandicFriendsTier.nullish(),
    pointsToNextTier: z.number().nullish(),
    nightsToTopTier: z.number().nullish(),
  })
  .merge(commonMembershipSchema)

export const membershipSchema = z.union([
  friendsMembershipSchema,
  sasMembershipSchema,
  otherMembershipSchema,
])

const pointExpirationSchema = z.object({
  points: z.number().int(),
  expires: z.string(),
})

export const userLoyaltySchema = z.object({
  memberships: z.array(membershipSchema),
  points: z.object({
    spendable: z.number().int(),
    earned: z.number().int(),
    spent: z.number().int(),
  }),
  tier: scandicFriendsTier,
  tierExpires: z.string(),
  tierBoostedBy: z.string().nullish(),
  pointExpirations: z.array(pointExpirationSchema),
})

export const getUserSchema = z
  .object({
    data: z.object({
      attributes: z.object({
        dateOfBirth: z.string().optional().default("1900-01-01"),
        email: z.string().email(),
        firstName: z.string(),
        language: z
          .string()
          // Preserve Profile v1 formatting for now so it matches ApiLang enum
          .transform((s) => s.charAt(0).toUpperCase() + s.slice(1))
          .optional(),
        lastName: z.string(),
        phoneNumber: z.string().optional(),
        profileId: z.string(),
        membershipNumber: z.string(),
        address: z
          .object({
            city: z.string().optional(),
            country: z.string().optional(),
            countryCode: z.nativeEnum(countriesMap).optional(),
            streetAddress: z.string().optional(),
            zipCode: z.string().optional(),
          })
          .optional()
          .nullable(),
        loyalty: userLoyaltySchema.optional(),
      }),
      type: z.string(),
    }),
  })
  .transform((apiResponse) => {
    return {
      ...apiResponse.data.attributes,
      membership: apiResponse.data.attributes.loyalty
        ? getFriendsMembership(apiResponse.data.attributes.loyalty)
        : null,
      name: `${apiResponse.data.attributes.firstName} ${apiResponse.data.attributes.lastName}`,
    }
  })

// Schema is the same for upcoming and previous stays endpoints
export const getStaysSchema = z.object({
  data: z.array(
    z.object({
      attributes: z.object({
        hotelOperaId: z.string(),
        hotelInformation: z.object({
          hotelContent: z.object({
            images: imageSchema,
          }),
          hotelName: z.string(),
          cityName: z.string().nullable(),
        }),
        confirmationNumber: z.string(),
        checkinDate: z.string(),
        checkoutDate: z.string(),
        isWebAppOrigin: z.boolean(),
        bookingUrl: z.string().default(""),
      }),
      relationships: z.object({
        hotel: z.object({
          links: z.object({
            related: z.string().nullable().optional(),
          }),
          data: z.object({
            id: z.string(),
            type: z.string(),
          }),
        }),
      }),
      type: z.string(),
      id: z.string(),
      links: z.object({
        self: z.object({
          href: z.string(),
          meta: z.object({
            method: z.string(),
          }),
        }),
      }),
    })
  ),
  links: z
    .object({
      self: z.string(),
      offset: z.number(),
      limit: z.number(),
      totalCount: z.number(),
    })
    .optional()
    .nullable(),
})

type GetStaysData = z.infer<typeof getStaysSchema>

export type Stay = GetStaysData["data"][number]

export const getFriendTransactionsSchema = z.object({
  data: z.array(
    z.object({
      attributes: z.object({
        awardPoints: z.number().default(0),
        checkinDate: z.string().default(""),
        checkoutDate: z.string().default(""),
        confirmationNumber: z.string().default(""),
        hotelOperaId: z.string().default(""),
        nights: z.number().default(1),
        pointsCalculated: z.boolean().default(true),
        transactionDate: z.string().default(""),
        bookingUrl: z.string().default(""),
        hotelInformation: z
          .object({
            city: z.string().default(""),
            name: z.string().default(""),
            hotelContent: z.object({
              images: imageSchema,
            }),
          })
          .optional(),
      }),
      relationships: z.object({
        booking: z.object({
          data: z.object({
            id: z.string().default(""),
            type: z.string().default(""),
          }),
          links: z.object({
            related: z.string().default(""),
          }),
        }),
        hotel: z
          .object({
            data: z.object({
              id: z.string().default(""),
              type: z.string().default(""),
            }),
            links: z.object({
              related: z.string().default(""),
            }),
          })
          .optional(),
      }),
      type: z.string().default(""),
    })
  ),
  links: z
    .object({
      self: z.string(),
    })
    .nullable(),
})

type GetFriendTransactionsData = z.infer<typeof getFriendTransactionsSchema>

export type FriendTransaction = GetFriendTransactionsData["data"][number]

export const creditCardSchema = z
  .object({
    attribute: z.object({
      cardName: z.string().optional(),
      alias: z.string(),
      truncatedNumber: z.string().transform((s) => s.slice(-4)),
      expirationDate: z.string(),
      cardType: z
        .string()
        .transform((s) => s.charAt(0).toLowerCase() + s.slice(1)),
    }),
    id: z.string(),
    type: z.string(),
  })
  .transform((apiResponse) => {
    return {
      id: apiResponse.id,
      type: apiResponse.attribute.cardType,
      truncatedNumber: apiResponse.attribute.truncatedNumber,
      alias: apiResponse.attribute.alias,
      expirationDate: apiResponse.attribute.expirationDate,
      cardType: apiResponse.attribute.cardType,
    }
  })

export const creditCardsSchema = z.object({
  data: z.array(creditCardSchema),
})

export const initiateSaveCardSchema = z.object({
  data: z.object({
    attribute: z.object({
      transactionId: z.string(),
      link: z.string(),
      mobileToken: z.string().optional(),
    }),
    type: z.string(),
  }),
})

export const subscriberIdSchema = z.object({
  subscriberId: z.string(),
})
