import { metrics } from "@opentelemetry/api"
import { cache } from "react"

import {
  MembershipLevel,
  MembershipLevelEnum,
} from "@/constants/membershipLevels"
import {
  GetAllLoyaltyLevels,
  GetLoyaltyLevel,
} from "@/lib/graphql/Query/LoyaltyLevels.graphql"
import { request } from "@/lib/graphql/request"
import { Context } from "@/server/context"
import { notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"

import { generateLoyaltyConfigTag } from "@/utils/generateTag"

import { loyaltyLevelInput } from "./input"
import { LoyaltyLevelsResponse, validateLoyaltyLevelsSchema } from "./output"

const meter = metrics.getMeter("trpc.loyaltyLevel")
// OpenTelemetry metrics: Loyalty Level
const getAllLoyaltyLevelCounter = meter.createCounter(
  "trpc.contentstack.loyaltyLevel.all"
)

const getAllLoyaltyLevelSuccessCounter = meter.createCounter(
  "trpc.contentstack.loyaltyLevel.all-success"
)
const getAllLoyaltyLevelFailCounter = meter.createCounter(
  "trpc.contentstack.loyaltyLevel.all-fail"
)

const getByLevelLoyaltyLevelCounter = meter.createCounter(
  "trpc.contentstack.loyaltyLevel.byLevel"
)

const getByLevelLoyaltyLevelSuccessCounter = meter.createCounter(
  "trpc.contentstack.loyaltyLevel.byLevel-success"
)
const getByLevelLoyaltyLevelFailCounter = meter.createCounter(
  "trpc.contentstack.loyaltyLevel.byLevel-fail"
)

export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
  getAllLoyaltyLevelCounter.add(1)

  // Ideally we should fetch all available tiers from API, but since they
  // are static, we can just use the enum values. We want to know which
  // levels we are fetching so that we can use tags to cache them
  const allLevelIds = Object.values(MembershipLevelEnum)

  const tags = allLevelIds.map((levelId) =>
    generateLoyaltyConfigTag(ctx.lang, "loyalty_level", levelId)
  )

  const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
    GetAllLoyaltyLevels,
    { lang: ctx.lang, level_ids: allLevelIds },
    { next: { tags }, cache: "force-cache" }
  )

  if (!loyaltyLevelsConfigResponse.data) {
    getAllLoyaltyLevelFailCounter.add(1)
    const notFoundError = notFound(loyaltyLevelsConfigResponse)
    console.error(
      "contentstack.loyaltyLevels not found error",
      JSON.stringify({
        query: {
          lang: ctx.lang,
        },
        error: { code: notFoundError.code },
      })
    )
    throw notFoundError
  }

  const validatedLoyaltyLevels = validateLoyaltyLevelsSchema.safeParse(
    loyaltyLevelsConfigResponse.data
  )
  if (!validatedLoyaltyLevels.success) {
    getAllLoyaltyLevelFailCounter.add(1)
    console.error(validatedLoyaltyLevels.error)
    console.error(
      "contentstack.rewards validation error",
      JSON.stringify({
        query: {
          lang: ctx.lang,
        },
        error: validatedLoyaltyLevels.error,
      })
    )
    return []
  }

  getAllLoyaltyLevelSuccessCounter.add(1)
  return validatedLoyaltyLevels.data
})

export const getLoyaltyLevel = cache(
  async (ctx: Context, level_id: MembershipLevel) => {
    getByLevelLoyaltyLevelCounter.add(1, {
      query: JSON.stringify({ lang: ctx.lang, level_id }),
    })

    const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
      GetLoyaltyLevel,
      { lang: ctx.lang, level_id },
      {
        next: {
          tags: [generateLoyaltyConfigTag(ctx.lang, "loyalty_level", level_id)],
        },
        cache: "force-cache",
      }
    )
    if (
      !loyaltyLevelsConfigResponse.data ||
      !loyaltyLevelsConfigResponse.data.all_loyalty_level.items.length
    ) {
      getByLevelLoyaltyLevelFailCounter.add(1)
      const notFoundError = notFound(loyaltyLevelsConfigResponse)
      console.error(
        "contentstack.loyaltyLevel not found error",
        JSON.stringify({
          query: { lang: ctx.lang, level_id },
          error: { code: notFoundError.code },
        })
      )
      throw notFoundError
    }

    const validatedLoyaltyLevels = validateLoyaltyLevelsSchema.safeParse(
      loyaltyLevelsConfigResponse.data
    )
    if (!validatedLoyaltyLevels.success) {
      getByLevelLoyaltyLevelFailCounter.add(1)
      console.error(validatedLoyaltyLevels.error)
      console.error(
        "contentstack.loyaltyLevel validation error",
        JSON.stringify({
          query: { lang: ctx.lang, level_id },
          error: validatedLoyaltyLevels.error,
        })
      )
      return null
    }

    getByLevelLoyaltyLevelSuccessCounter.add(1)
    return validatedLoyaltyLevels.data[0]
  }
)

export const loyaltyLevelQueryRouter = router({
  byLevel: contentstackBaseProcedure
    .input(loyaltyLevelInput)
    .query(async function ({ ctx, input }) {
      return getLoyaltyLevel(ctx, input.level)
    }),
  all: contentstackBaseProcedure.query(async function ({ ctx }) {
    return getAllLoyaltyLevels(ctx)
  }),
})
