import "server-only"

import { ClientError, type GraphQLClient } from "graphql-request"

import { Lang } from "@/constants/languages"
import { env } from "@/env/server"

import type { DocumentNode } from "graphql"

import type { Data } from "@/types/request"

export async function request<T>(
  client: GraphQLClient,
  query: string | DocumentNode,
  variables?: {},
  params?: RequestInit
): Promise<Data<T>> {
  try {
    client.setHeaders({
      access_token: env.CMS_ACCESS_TOKEN,
      branch: env.CMS_BRANCH,
      "Content-Type": "application/json",
      ...params?.headers,
    })

    client.requestConfig.cache = params?.cache
    client.requestConfig.next = params?.next

    if (env.PRINT_QUERY) {
      const print = (await import("graphql/language/printer")).print
      const rawResponse = await client.rawRequest<T>(
        print(query as DocumentNode),
        variables,
        {
          access_token: env.CMS_ACCESS_TOKEN,
          "Content-Type": "application/json",
        }
      )

      /**
       * TODO: Send to Monitoring (Logging and Metrics)
       */
      console.log({
        complexityLimit: rawResponse.headers.get("x-query-complexity"),
      })
      console.log({
        referenceDepth: rawResponse.headers.get("x-reference-depth"),
      })
      console.log({
        resolverCost: rawResponse.headers.get("x-resolver-cost"),
      })

      return {
        data: rawResponse.data,
      }
    }

    try {
      // @ts-expect-error: query can be undefined (?)
      const operationName = (query as DocumentNode).definitions.find(
        (d) => d.kind === "OperationDefinition" && d.operation === "query"
        // @ts-expect-error: name does not exist (?)
      ).name.value

      console.log(`[gql] Sending graphql request to ${env.CMS_URL}`, {
        operationName,
        variables,
      })
    } catch (e) {
      console.error(`[gql] Unable to extract operation name from query`, {
        query,
        error: e,
      })

      console.log(`[gql] Sending graphql request to ${env.CMS_URL}`, {
        operationName: "<Unable to extract>",
        variables,
      })
    }

    const response = await client.request<T>({
      document: query,
      variables,
      requestHeaders: {
        "User-Agent": `scandic-web-${env.SENTRY_ENVIRONMENT}`,
      },
    })

    try {
      // @ts-expect-error: query can be undefined (?)
      const operationName = (query as DocumentNode).definitions.find(
        (d) => d.kind === "OperationDefinition" && d.operation === "query"
        // @ts-expect-error: name does not exist (?)
      ).name.value

      console.log(`[gql] Response for ${env.CMS_URL}`, {
        response,
        operationName,
        variables,
      })
    } catch (e) {
      console.error(`[gql] Unable to extract operation name from query`, {
        query,
        error: e,
      })

      console.log(`[gql] Response for ${env.CMS_URL}`, {
        response,
        operationName: "<Unable to extract>",
        variables,
      })
    }

    return { data: response }
  } catch (error) {
    if (error instanceof ClientError) {
      if (error.response.errors?.length) {
        const failedToFetchItem = error.response.errors.find(
          (err) => err.message === "Failed to fetch item"
        )
        if (
          failedToFetchItem &&
          failedToFetchItem.extensions?.errors === "Object not found!" &&
          failedToFetchItem.path?.find((p) => Lang[p as Lang])
        ) {
          /**
           * Because of Contentstacks totally obscure way of implementing
           * GraphQL where they throw an error when you are querying for
           * a single item that is nullable and doesn't exist. This leads
           * to the issue where when we have a page that is missing a published
           * version for one language, it throws an error which we have to recover
           * from here since it isn't an error.
           */

          return { data: error.response.data as T }
        }
      }
    }

    console.error(
      `[gql] Error sending graphql request to ${env.CMS_URL}`,
      error
    )
    throw new Error("Something went wrong")
  }
}
