import * as api from "@/lib/api"
import { createCounter } from "@/server/telemetry"
import {
  languageProtectedProcedure,
  protectedProcedure,
  router,
  safeProtectedProcedure,
} from "@/server/trpc"
import { toApiLang } from "@/server/utils"

import { isValidSession } from "@/utils/session"
import { getFriendsMembership, getMembershipCards } from "@/utils/user"

import {
  friendTransactionsInput,
  getSavedPaymentCardsInput,
  staysInput,
  userTrackingInput,
} from "./input"
import { getFriendTransactionsSchema } from "./output"
import {
  getCreditCards,
  getPreviousStays,
  getUpcomingStays,
  getVerifiedUser,
  parsedUser,
  updateStaysBookingUrl,
} from "./utils"

import type {
  LoginType,
  TrackingSDKUserData,
} from "@/types/components/tracking"
import { Transactions } from "@/types/enums/transactions"

export const userQueryRouter = router({
  get: protectedProcedure
    .use(async function (opts) {
      return opts.next({
        ctx: {
          ...opts.ctx,
          isMFA:
            opts.ctx.session.token.mfa_scope &&
            opts.ctx.session.token.mfa_expires_at > Date.now(),
        },
      })
    })
    .query(async function getUser({ ctx }) {
      const data = await getVerifiedUser({ session: ctx.session })

      if (!data) {
        return null
      }

      if ("error" in data && data.error) {
        return data
      }

      return parsedUser(data.data, ctx.isMFA)
    }),
  getSafely: safeProtectedProcedure.query(async function getUser({ ctx }) {
    if (!isValidSession(ctx.session)) {
      return null
    }

    const data = await getVerifiedUser({ session: ctx.session })

    if (!data || "error" in data) {
      return null
    }

    return parsedUser(data.data, true)
  }),
  getWithExtendedPartnerData: safeProtectedProcedure.query(
    async function getUser({ ctx }) {
      if (!isValidSession(ctx.session)) {
        return null
      }

      const data = await getVerifiedUser({
        session: ctx.session,
        includeExtendedPartnerData: true,
      })

      if (!data || "error" in data) {
        return null
      }

      return parsedUser(data.data, true)
    }
  ),
  name: safeProtectedProcedure.query(async function ({ ctx }) {
    if (!isValidSession(ctx.session)) {
      return null
    }
    const verifiedData = await getVerifiedUser({ session: ctx.session })

    if (!verifiedData || "error" in verifiedData) {
      return null
    }
    return {
      firstName: verifiedData.data.firstName,
      lastName: verifiedData.data.lastName,
    }
  }),
  membershipLevel: protectedProcedure.query(async function ({ ctx }) {
    const verifiedData = await getVerifiedUser({ session: ctx.session })
    if (
      !verifiedData ||
      "error" in verifiedData ||
      !verifiedData.data.loyalty
    ) {
      return null
    }

    const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
    return membershipLevel
  }),
  safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
    if (!isValidSession(ctx.session)) {
      return null
    }
    const verifiedData = await getVerifiedUser({ session: ctx.session })

    if (
      !verifiedData ||
      "error" in verifiedData ||
      !verifiedData.data.loyalty
    ) {
      return null
    }

    const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
    return membershipLevel
  }),
  userTrackingInfo: safeProtectedProcedure
    .input(userTrackingInput)
    .query(async function ({ ctx, input }) {
      const { lang } = input
      const language = lang || ctx.lang

      const userTrackingInfoCounter = createCounter("user", "userTrackingInfo")
      const metricsUserTrackingInfo = userTrackingInfoCounter.init()

      metricsUserTrackingInfo.start()

      const notLoggedInUserTrackingData: TrackingSDKUserData = {
        loginStatus: "Non-logged in",
      }

      if (!isValidSession(ctx.session)) {
        metricsUserTrackingInfo.success({
          reason: "invalid session",
          data: notLoggedInUserTrackingData,
        })
        return notLoggedInUserTrackingData
      }

      try {
        const verifiedUserData = await getVerifiedUser({ session: ctx.session })

        if (
          !verifiedUserData ||
          "error" in verifiedUserData ||
          !verifiedUserData.data.loyalty
        ) {
          metricsUserTrackingInfo.success({
            reason: "invalid user data",
            data: notLoggedInUserTrackingData,
          })
          return notLoggedInUserTrackingData
        }

        const previousStaysData = await getPreviousStays(
          ctx.session.token.access_token,
          1,
          language
        )
        if (!previousStaysData) {
          metricsUserTrackingInfo.success({
            reason: "no previous stays data",
            data: notLoggedInUserTrackingData,
          })
          return notLoggedInUserTrackingData
        }

        const membership = getFriendsMembership(verifiedUserData.data.loyalty)

        const loggedInUserTrackingData: TrackingSDKUserData = {
          loginStatus: "logged in",
          loginType: ctx.session.token.loginType as LoginType,
          memberId: verifiedUserData.data.profileId,
          membershipNumber: membership?.membershipNumber,
          memberLevel: membership?.membershipLevel,
          noOfNightsStayed: previousStaysData.links?.totalCount ?? 0,
          totalPointsAvailableToSpend: membership?.currentPoints,
          loginAction: "login success",
        }

        metricsUserTrackingInfo.success({
          reason: "valid logged in",
          data: loggedInUserTrackingData,
        })

        return loggedInUserTrackingData
      } catch (error) {
        metricsUserTrackingInfo.fail(error)
        return notLoggedInUserTrackingData
      }
    }),

  stays: router({
    previous: languageProtectedProcedure
      .input(staysInput)
      .query(async ({ ctx, input }) => {
        const { limit, cursor, lang } = input
        const language = lang || ctx.lang

        const data = await getPreviousStays(
          ctx.session.token.access_token,
          limit,
          language,
          cursor
        )

        if (data) {
          const nextCursor =
            data.links && data.links.offset < data.links.totalCount
              ? data.links.offset
              : undefined

          const updatedData = await updateStaysBookingUrl(
            data.data,
            ctx.session,
            language
          )

          return {
            data: updatedData,
            nextCursor,
          }
        }
        return null
      }),

    upcoming: languageProtectedProcedure
      .input(staysInput)
      .query(async ({ ctx, input }) => {
        const { limit, cursor, lang } = input
        const language = lang || ctx.lang

        const data = await getUpcomingStays(
          ctx.session.token.access_token,
          limit,
          language,
          cursor
        )

        if (data) {
          const nextCursor =
            data.links && data.links.offset < data.links.totalCount
              ? data.links.offset
              : undefined

          const updatedData = await updateStaysBookingUrl(
            data.data,
            ctx.session,
            language
          )

          return {
            data: updatedData,
            nextCursor,
          }
        }
        return null
      }),
  }),
  transaction: router({
    friendTransactions: languageProtectedProcedure
      .input(friendTransactionsInput)
      .query(async ({ ctx, input }) => {
        const { limit, page, lang } = input

        const friendTransactionsCounter = createCounter(
          "trpc.user.transactions",
          "friendTransactions"
        )

        const metricsFriendTransactions = friendTransactionsCounter.init({
          limit,
          page,
          lang,
        })

        metricsFriendTransactions.start()

        const language = lang ?? ctx.lang

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

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

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

        const updatedData = await updateStaysBookingUrl(
          verifiedData.data.data,
          ctx.session,
          ctx.lang
        )

        const pageData = updatedData
          .filter((t) => t.type !== Transactions.rewardType.expired)
          .sort((a, b) => {
            // 'BALFWD' are transactions from Opera migration that happended in May 2021
            if (a.attributes.confirmationNumber === "BALFWD") return 1
            if (b.attributes.confirmationNumber === "BALFWD") return -1

            const dateA = new Date(
              a.attributes.checkinDate
                ? a.attributes.checkinDate
                : a.attributes.transactionDate
            )
            const dateB = new Date(
              b.attributes.checkinDate
                ? b.attributes.checkinDate
                : b.attributes.transactionDate
            )

            return dateA > dateB ? -1 : 1
          })

        const slicedData = pageData.slice(limit * (page - 1), limit * page)

        const result = {
          data: {
            transactions: slicedData.map(({ type, attributes }) => {
              return {
                type,
                awardPoints: attributes.awardPoints,
                checkinDate: attributes.checkinDate,
                checkoutDate: attributes.checkoutDate,
                city: attributes.hotelInformation?.city,
                confirmationNumber: attributes.confirmationNumber,
                hotelName: attributes.hotelInformation?.name,
                nights: attributes.nights,
                pointsCalculated: attributes.pointsCalculated,
                hotelId: attributes.hotelOperaId,
                transactionDate: attributes.transactionDate,
                bookingUrl: attributes.bookingUrl,
              }
            }),
          },
          meta: {
            totalPages: Math.ceil(pageData.length / limit),
          },
        }

        metricsFriendTransactions.success()

        return result
      }),
  }),

  creditCards: protectedProcedure.query(async function ({ ctx }) {
    return await getCreditCards({ session: ctx.session })
  }),
  safePaymentCards: safeProtectedProcedure
    .input(getSavedPaymentCardsInput)
    .query(async function ({ ctx, input }) {
      if (!isValidSession(ctx.session)) {
        return null
      }

      const savedCards = await getCreditCards({
        session: ctx.session,
        onlyNonExpired: true,
      })

      if (!savedCards) {
        return null
      }

      return savedCards.filter((card) =>
        input.supportedCards.includes(card.type)
      )
    }),

  membershipCards: protectedProcedure.query(async function ({ ctx }) {
    const userData = await getVerifiedUser({ session: ctx.session })

    if (!userData || "error" in userData || !userData.data.loyalty) {
      return null
    }

    return getMembershipCards(userData.data.loyalty)
  }),
})
