import * as api from "@/lib/api"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
  contentStackBaseWithProtectedProcedure,
  contentStackBaseWithServiceProcedure,
  protectedProcedure,
  router,
} from "@/server/trpc"
import { langInput } from "@/server/utils"

import {
  getRedeemableRewards,
  getUnwrappedSurpriseRewards,
} from "@/utils/rewards"

import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query"
import {
  rewardsAllInput,
  rewardsByLevelInput,
  rewardsRedeemInput,
  rewardsUpdateInput,
} from "./input"
import { validateCategorizedRewardsSchema } from "./output"
import {
  getCachedAllTierRewards,
  getCmsRewards,
  getUniqueRewardIds,
} from "./utils"

import type { BaseReward, Surprise } from "@/types/components/myPages/rewards"
import type { LevelWithRewards } from "@/types/components/overviewTable"
import type { CMSReward } from "@/types/trpc/routers/contentstack/reward"

export const rewardQueryRouter = router({
  all: contentStackBaseWithServiceProcedure
    .input(rewardsAllInput)
    .query(async function ({ input, ctx }) {
      const getContentstackRewardAllCounter = createCounter(
        "trpc.contentstack",
        "reward.all"
      )
      const metricsGetContentstackRewardAll =
        getContentstackRewardAllCounter.init()

      metricsGetContentstackRewardAll.start()

      const allApiRewards = await getCachedAllTierRewards(ctx.serviceToken)

      if (!allApiRewards) {
        return []
      }

      const rewardIds = Object.values(allApiRewards)
        .flatMap((level) => level.map((reward) => reward?.rewardId))
        .filter((id): id is string => Boolean(id))

      const contentStackRewards = await getCmsRewards(
        ctx.lang,
        getUniqueRewardIds(rewardIds)
      )

      if (!contentStackRewards) {
        return []
      }

      const loyaltyLevelsConfig = await getAllLoyaltyLevels(ctx)
      const levelsWithRewards = Object.entries(allApiRewards).map(
        ([level, rewards]) => {
          const combinedRewards = rewards
            .filter((reward) =>
              input.unique ? reward.rewardTierLevel === level : true
            )
            .map((reward) => {
              const contentStackReward = contentStackRewards.find((r) => {
                return r.reward_id === reward.rewardId
              })

              if (contentStackReward) {
                return contentStackReward
              } else {
                metricsGetContentstackRewardAll.dataError(
                  `Failed to find reward in CMS for reward ${reward.rewardId} `,
                  {
                    rewardId: reward.rewardId,
                  }
                )
              }
            })
            .filter((reward): reward is CMSReward => Boolean(reward))

          const levelConfig = loyaltyLevelsConfig.find(
            (l) => l.level_id === level
          )

          if (!levelConfig) {
            metricsGetContentstackRewardAll.dataError(
              `Failed to matched loyalty level between API and CMS for level ${level}`
            )
            throw notFound()
          }
          const result: LevelWithRewards = {
            ...levelConfig,
            rewards: combinedRewards,
          }
          return result
        }
      )

      metricsGetContentstackRewardAll.success()

      return levelsWithRewards
    }),
  byLevel: contentStackBaseWithServiceProcedure
    .input(rewardsByLevelInput)
    .query(async function ({ input, ctx }) {
      const { level_id } = input

      const getRewardByLevelCounter = createCounter(
        "trpc.contentstack",
        "reward.byLevel"
      )
      const metricsGetRewardByLevel = getRewardByLevelCounter.init({
        level_id,
      })

      metricsGetRewardByLevel.start()

      const allUpcomingApiRewards = await getCachedAllTierRewards(
        ctx.serviceToken
      )

      if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) {
        metricsGetRewardByLevel.noDataError()

        return null
      }

      let apiRewards = allUpcomingApiRewards[level_id]!

      if (input.unique) {
        apiRewards = allUpcomingApiRewards[level_id]!.filter(
          (reward) => reward?.rewardTierLevel === level_id
        )
      }

      const rewardIds = apiRewards
        .map((reward) => reward?.rewardId)
        .filter((id): id is string => Boolean(id))

      const [contentStackRewards, loyaltyLevelsConfig] = await Promise.all([
        getCmsRewards(ctx.lang, rewardIds),
        getLoyaltyLevel(ctx, input.level_id),
      ])

      if (!contentStackRewards) {
        return null
      }

      const levelsWithRewards = apiRewards
        .map((reward) => {
          const contentStackReward = contentStackRewards.find((r) => {
            return r.reward_id === reward.rewardId
          })

          if (contentStackReward) {
            return contentStackReward
          } else {
            metricsGetRewardByLevel.dataError(
              `Failed to find reward in Contentstack with rewardId: ${reward.rewardId}`,
              {
                rewardId: reward.rewardId,
              }
            )
          }
        })
        .filter((reward): reward is CMSReward => Boolean(reward))

      metricsGetRewardByLevel.success()

      return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
    }),
  current: contentStackBaseWithProtectedProcedure
    .input(langInput.optional()) // lang is required for client, but not for server
    .query(async function ({ ctx }) {
      const getCurrentRewardCounter = createCounter(
        "trpc.contentstack",
        "reward.current"
      )
      const metricsGetCurrentReward = getCurrentRewardCounter.init()

      metricsGetCurrentReward.start()

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

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

      const data = await apiResponse.json()

      const validatedApiRewards =
        validateCategorizedRewardsSchema.safeParse(data)

      if (!validatedApiRewards.success) {
        metricsGetCurrentReward.validationError(validatedApiRewards.error)
        return null
      }

      const { benefits, coupons } = validatedApiRewards.data
      const redeemableRewards = getRedeemableRewards([...benefits, ...coupons])
      const rewardIds = redeemableRewards.map(({ rewardId }) => rewardId).sort()
      const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
      if (!cmsRewards) {
        return null
      }

      const rewards: BaseReward[] = cmsRewards.map((cmsReward) => {
        // Non-null assertion is used here because we know our reward exist
        const apiReward = redeemableRewards.find(
          ({ rewardId }) => rewardId === cmsReward.reward_id
        )!

        return {
          ...apiReward,
          ...cmsReward,
        }
      })

      metricsGetCurrentReward.success()

      return { rewards }
    }),
  surprises: contentStackBaseWithProtectedProcedure
    .input(langInput.optional()) // lang is required for client, but not for server
    .query(async ({ ctx }) => {
      const getSurprisesCounter = createCounter(
        "trpc.contentstack",
        "surprises"
      )
      const metricsGetSurprises = getSurprisesCounter.init()

      metricsGetSurprises.start()

      const endpoint = api.endpoints.v1.Profile.Reward.reward

      const apiResponse = await api.get(endpoint, {
        cache: undefined,
        headers: {
          Authorization: `Bearer ${ctx.session.token.access_token}`,
        },
      })

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

      const data = await apiResponse.json()
      const validatedApiRewards =
        validateCategorizedRewardsSchema.safeParse(data)

      if (!validatedApiRewards.success) {
        metricsGetSurprises.validationError(validatedApiRewards.error)
        return null
      }

      const unwrappedSurpriseRewards = getUnwrappedSurpriseRewards(
        validatedApiRewards.data.coupons
      )
      const rewardIds = unwrappedSurpriseRewards
        .map(({ rewardId }) => rewardId)
        .sort()
      const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
      if (!cmsRewards) {
        return null
      }

      const surprises: Surprise[] = cmsRewards.map((cmsReward) => {
        // Non-null assertion is used here because we know our reward exist
        const apiReward = unwrappedSurpriseRewards.find(
          ({ rewardId }) => rewardId === cmsReward.reward_id
        )!

        return {
          ...apiReward,
          ...cmsReward,
        }
      })

      metricsGetSurprises.success()

      return surprises
    }),
  unwrap: protectedProcedure
    .input(rewardsUpdateInput)
    .mutation(async ({ input, ctx }) => {
      const results = await Promise.allSettled(
        // Execute each unwrap individually
        input.map(({ rewardId, couponCode }) => {
          async function handleUnwrap() {
            const getUnwrapSurpriseCounter = createCounter(
              "trpc.contentstack",
              "reward.unwrap"
            )

            const metricsGetUnwrapSurprise = getUnwrapSurpriseCounter.init({
              rewardId,
              couponCode,
            })

            metricsGetUnwrapSurprise.start()

            const apiResponse = await api.post(
              api.endpoints.v1.Profile.Reward.unwrap,
              {
                body: {
                  rewardId,
                  couponCode,
                },
                headers: {
                  Authorization: `Bearer ${ctx.session.token.access_token}`,
                },
              }
            )

            if (!apiResponse.ok) {
              metricsGetUnwrapSurprise.httpError(apiResponse)
              return false
            }

            metricsGetUnwrapSurprise.success()

            return true
          }

          return handleUnwrap()
        })
      )

      if (
        results.some(
          (result) => result.status === "rejected" || result.value === false
        )
      ) {
        return null
      }

      return true
    }),
  redeem: protectedProcedure
    .input(rewardsRedeemInput)
    .mutation(async ({ input, ctx }) => {
      const { rewardId, couponCode } = input

      const getRedeemCounter = createCounter(
        "trpc.contentstack",
        "reward.redeem"
      )

      const metricGetRedeem = getRedeemCounter.init({ rewardId, couponCode })

      metricGetRedeem.start()

      const apiResponse = await api.post(
        api.endpoints.v1.Profile.Reward.redeem,
        {
          body: {
            rewardId,
            couponCode,
          },
          headers: {
            Authorization: `Bearer ${ctx.session.token.access_token}`,
          },
        }
      )

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

      metricGetRedeem.success()

      return true
    }),
})
