import { countries } from "@/constants/countries"
import { myBookingPath } from "@/constants/myBooking"
import { myStay } from "@/constants/routes/myStay"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { createCounter } from "@/server/telemetry"
import { toApiLang } from "@/server/utils"

import { cache } from "@/utils/cache"
import { encrypt } from "@/utils/encryption"
import * as maskValue from "@/utils/maskValue"
import { isValidSession } from "@/utils/session"
import { getCurrentWebUrl } from "@/utils/url"
import { getFriendsMembership } from "@/utils/user"

import {
  creditCardsSchema,
  type FriendTransaction,
  getStaysSchema,
  getUserSchema,
  type Stay,
} from "./output"

import type { Session } from "next-auth"

import type { User } from "@/types/user"
import type { Lang } from "@/constants/languages"

export const getVerifiedUser = cache(
  async ({
    session,
    includeExtendedPartnerData,
  }: {
    session: Session
    includeExtendedPartnerData?: boolean
  }) => {
    const getVerifiedUserCounter = createCounter("user", "getVerifiedUser")
    const metricsGetVerifiedUser = getVerifiedUserCounter.init()

    metricsGetVerifiedUser.start()

    const now = Date.now()
    if (session.token.expires_at && session.token.expires_at < now) {
      metricsGetVerifiedUser.dataError(`Token expired`)
      return { error: true, cause: "token_expired" } as const
    }

    const apiResponse = await api.get(
      api.endpoints.v2.Profile.profile,
      {
        headers: {
          Authorization: `Bearer ${session.token.access_token}`,
        },
      },
      includeExtendedPartnerData
        ? { includes: "extendedPartnerInformation" }
        : {}
    )

    if (!apiResponse.ok) {
      await metricsGetVerifiedUser.httpError(apiResponse)

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

      return {
        error: true,
        cause: "unknown",
        status: apiResponse.status,
      } as const
    }

    const apiJson = await apiResponse.json()
    if (!apiJson.data?.attributes) {
      metricsGetVerifiedUser.dataError(
        `Missing data attributes in API response`,
        {
          data: apiJson,
        }
      )
      return null
    }

    const verifiedData = getUserSchema.safeParse(apiJson)
    if (!verifiedData.success) {
      metricsGetVerifiedUser.validationError(verifiedData.error)
      return null
    }

    metricsGetVerifiedUser.success()

    return verifiedData
  }
)

export async function getMembershipNumber(
  session: Session | null
): Promise<string | undefined> {
  if (!isValidSession(session)) return undefined

  const verifiedUser = await getVerifiedUser({ session })
  if (!verifiedUser || "error" in verifiedUser) {
    return undefined
  }

  return verifiedUser.data.membershipNumber
}

export async function getPreviousStays(
  accessToken: string,
  limit: number = 10,
  language: Lang,
  cursor?: string
) {
  const getPreviousStaysCounter = createCounter("user", "getPreviousStays")
  const metricsGetPreviousStays = getPreviousStaysCounter.init({
    limit,
    cursor,
    language,
  })

  metricsGetPreviousStays.start()

  const params: Record<string, string> = {
    limit: String(limit),
    language: toApiLang(language),
  }

  if (cursor) {
    params.offset = cursor
  }

  const apiResponse = await api.get(
    api.endpoints.v1.Booking.Stays.past,
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    },
    params
  )

  if (!apiResponse.ok) {
    await metricsGetPreviousStays.httpError(apiResponse)
    return null
  }

  const apiJson = await apiResponse.json()

  const verifiedData = getStaysSchema.safeParse(apiJson)
  if (!verifiedData.success) {
    metricsGetPreviousStays.validationError(verifiedData.error)
    return null
  }

  metricsGetPreviousStays.success()

  return verifiedData.data
}

export async function getUpcomingStays(
  accessToken: string,
  limit: number = 10,
  language: Lang,
  cursor?: string
) {
  const getUpcomingStaysCounter = createCounter("user", "getUpcomingStays")
  const metricsGetUpcomingStays = getUpcomingStaysCounter.init({
    limit,
    cursor,
    language,
  })

  metricsGetUpcomingStays.start()

  const params: Record<string, string> = {
    limit: String(limit),
    language: toApiLang(language),
  }

  if (cursor) {
    params.offset = cursor
  }

  const apiResponse = await api.get(
    api.endpoints.v1.Booking.Stays.future,
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    },
    params
  )

  if (!apiResponse.ok) {
    await metricsGetUpcomingStays.httpError(apiResponse)
    return null
  }

  const apiJson = await apiResponse.json()

  const verifiedData = getStaysSchema.safeParse(apiJson)
  if (!verifiedData.success) {
    metricsGetUpcomingStays.validationError(verifiedData.error)
    return null
  }

  metricsGetUpcomingStays.success()

  return verifiedData.data
}

export function parsedUser(data: User, isMFA: boolean) {
  const country = countries.find((c) => c.code === data.address?.countryCode)

  const user = {
    address: {
      city: data.address?.city,
      country: country?.name ?? "",
      countryCode: data.address?.countryCode,
      streetAddress: data.address?.streetAddress,
      zipCode: data.address?.zipCode,
    },
    dateOfBirth: data.dateOfBirth,
    email: data.email,
    firstName: data.firstName,
    language: data.language,
    lastName: data.lastName,
    membershipNumber: data.membershipNumber,
    membership: data.loyalty ? getFriendsMembership(data.loyalty) : null,
    loyalty: data.loyalty,
    name: `${data.firstName} ${data.lastName}`,
    phoneNumber: data.phoneNumber,
    profileId: data.profileId,
  }

  if (!isMFA) {
    if (user.address.city) {
      user.address.city = maskValue.text(user.address.city)
    }
    if (user.address.streetAddress) {
      user.address.streetAddress = maskValue.text(user.address.streetAddress)
    }

    user.address.zipCode = data.address?.zipCode
      ? maskValue.text(data.address.zipCode)
      : ""

    user.dateOfBirth = maskValue.all(user.dateOfBirth)

    user.email = maskValue.email(user.email)

    user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : ""
  }

  return user
}

export const getCreditCards = cache(
  async ({
    session,
    onlyNonExpired,
  }: {
    session: Session
    onlyNonExpired?: boolean
  }) => {
    const getCreditCardsCounter = createCounter("user", "getCreditCards")
    const metricsGetCreditCards = getCreditCardsCounter.init({
      onlyNonExpired,
    })

    metricsGetCreditCards.start()

    const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, {
      headers: {
        Authorization: `Bearer ${session.token.access_token}`,
      },
    })

    if (!apiResponse.ok) {
      await metricsGetCreditCards.httpError(apiResponse)
      return null
    }

    const apiJson = await apiResponse.json()
    const verifiedData = creditCardsSchema.safeParse(apiJson)
    if (!verifiedData.success) {
      metricsGetCreditCards.validationError(verifiedData.error)
      return null
    }

    const result = verifiedData.data.data.filter((card) => {
      if (onlyNonExpired) {
        try {
          const expirationDate = dt(card.expirationDate).startOf("day")
          const currentDate = dt().startOf("day")
          return expirationDate > currentDate
        } catch (_) {
          return false
        }
      }
      return true
    })

    metricsGetCreditCards.success()

    return result
  }
)

export async function updateStaysBookingUrl(
  data: Stay[],
  session: Session,
  lang: Lang
): Promise<Stay[]>

export async function updateStaysBookingUrl(
  data: FriendTransaction[],
  session: Session,
  lang: Lang
): Promise<FriendTransaction[]>

export async function updateStaysBookingUrl(
  data: Stay[] | FriendTransaction[],
  session: Session,
  lang: Lang
) {
  const user = await getVerifiedUser({
    session,
  })

  if (user && !("error" in user)) {
    return data.map((d) => {
      const originalString =
        d.attributes.confirmationNumber.toString() + "," + user.data.lastName
      const encryptedBookingValue = encrypt(originalString)

      // Get base URL with fallback for ephemeral environments (like deploy previews).
      const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"

      // Construct Booking URL.
      const bookingUrl = !env.isLangLive(lang)
        ? new URL(
            getCurrentWebUrl({
              path: myBookingPath[lang],
              lang,
              baseUrl,
            })
          )
        : new URL(myStay[lang], baseUrl)

      // Add search parameters.
      if (encryptedBookingValue) {
        bookingUrl.searchParams.set("RefId", encryptedBookingValue)
      } else {
        bookingUrl.searchParams.set("lastName", user.data.lastName)
        bookingUrl.searchParams.set(
          "bookingId",
          d.attributes.confirmationNumber.toString()
        )
      }

      return {
        ...d,
        attributes: {
          ...d.attributes,
          bookingUrl: bookingUrl.toString(),
        },
      }
    })
  }

  return data
}
