import { metrics } from "@opentelemetry/api"
import { revalidateTag, unstable_cache } from "next/cache"

import { env } from "@/env/server"

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

import { ServiceTokenResponse } from "@/types/tokens"

// OpenTelemetry metrics: Service token
const meter = metrics.getMeter("trpc.context.serviceToken")
const fetchServiceTokenCounter = meter.createCounter(
  "trpc.context.serviceToken.fetch-new-token"
)
const fetchTempServiceTokenCounter = meter.createCounter(
  "trpc.context.serviceToken.fetch-temporary"
)
const fetchServiceTokenFailCounter = meter.createCounter(
  "trpc.context.serviceToken.fetch-fail"
)

async function fetchServiceToken(scopes: string[]) {
  fetchServiceTokenCounter.add(1)

  const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Accept: "application/json",
    },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: env.CURITY_CLIENT_ID_SERVICE,
      client_secret: env.CURITY_CLIENT_SECRET_SERVICE,
      scope: scopes.join(" "),
    }),
  })

  if (!response.ok) {
    const text = await response.text()
    const error = {
      status: response.status,
      statusText: response.statusText,
      text,
    }

    fetchServiceTokenFailCounter.add(1, {
      error_type: "http_error",
      error: JSON.stringify(error),
    })

    console.error(
      "fetchServiceToken error",
      JSON.stringify({
        query: {
          grant_type: "client_credentials",
          client_id: env.CURITY_CLIENT_ID_SERVICE,
          scope: scopes.join(" "),
        },
        error,
      })
    )

    throw new Error(
      `[fetchServiceToken] Failed to obtain service token: ${JSON.stringify(error)}`
    )
  }

  return response.json() as Promise<ServiceTokenResponse>
}

export async function getServiceToken() {
  let scopes: string[] = []
  if (env.HIDE_FOR_NEXT_RELEASE) {
    scopes = ["profile"]
  } else {
    scopes = ["profile", "hotel", "booking", "package"]
  }
  const tag = generateServiceTokenTag(scopes)
  const getCachedJwt = unstable_cache(
    async (scopes) => {
      const jwt = await fetchServiceToken(scopes)

      const expiresAt = Date.now() + jwt.expires_in * 1000
      return { expiresAt, jwt }
    },
    [tag],
    { tags: [tag] }
  )

  const cachedJwt = await getCachedJwt(scopes)
  if (cachedJwt.expiresAt < Date.now()) {
    console.log(
      "trpc.context.serviceToken: Service token expired, revalidating tag"
    )

    revalidateTag(tag)

    console.log(
      "trpc.context.serviceToken: Fetching new temporary service token."
    )
    fetchTempServiceTokenCounter.add(1)
    const newToken = await fetchServiceToken(scopes)
    return newToken
  }

  return cachedJwt.jwt
}
