import merge from "deepmerge"
import fetchRetry from "fetch-retry"

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

import type {
  RequestOptionsWithJSONBody,
  RequestOptionsWithOutBody,
} from "@/types/fetch"
import type { Endpoint } from "./endpoints"

export { endpoints } from "./endpoints"

const defaultOptions: RequestInit = {
  cache: "no-store",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  mode: "cors",
}

const wrappedFetch = fetchRetry(fetch, {
  retries: 3,
  retryDelay: function (attempt) {
    return Math.pow(2, attempt) * 150 // 150, 300, 600
  },
})

export async function get(
  endpoint: Endpoint,
  options: RequestOptionsWithOutBody,
  params = {}
) {
  const url = new URL(env.API_BASEURL)
  url.pathname = endpoint
  url.search = new URLSearchParams(params).toString()
  return wrappedFetch(
    url,
    merge.all([defaultOptions, { method: "GET" }, options])
  )
}

export async function patch(
  endpoint: Endpoint | `${Endpoint}/${string}`,
  options: RequestOptionsWithJSONBody,
  params = {}
) {
  const { body, ...requestOptions } = options
  const url = new URL(env.API_BASEURL)
  url.pathname = endpoint
  url.search = new URLSearchParams(params).toString()
  return wrappedFetch(
    url,
    merge.all([
      defaultOptions,
      { body: JSON.stringify(body), method: "PATCH" },
      requestOptions,
    ])
  )
}

export async function post(
  endpoint: Endpoint | `${Endpoint}/${string}`,
  options: RequestOptionsWithJSONBody,
  params = {}
) {
  const { body, ...requestOptions } = options
  const url = new URL(env.API_BASEURL)
  url.pathname = endpoint
  url.search = new URLSearchParams(params).toString()
  return wrappedFetch(
    url,
    merge.all([
      defaultOptions,
      { body: JSON.stringify(body), method: "POST" },
      requestOptions,
    ])
  )
}

export async function put(
  endpoint: Endpoint | `${Endpoint}/${string}`,
  options: RequestOptionsWithJSONBody,
  params = {}
) {
  const { body, ...requestOptions } = options
  const url = new URL(env.API_BASEURL)
  url.pathname = endpoint
  url.search = new URLSearchParams(params).toString()
  return wrappedFetch(
    url,
    merge.all([
      defaultOptions,
      { body: JSON.stringify(body), method: "PUT" },
      requestOptions,
    ])
  )
}

export async function remove(
  endpoint: Endpoint | `${Endpoint}/${string}`,
  options: RequestOptionsWithJSONBody,
  params = {}
) {
  const { body, ...requestOptions } = options
  const url = new URL(env.API_BASEURL)
  url.pathname = endpoint
  url.search = new URLSearchParams(params).toString()
  return wrappedFetch(
    url,
    merge.all([
      defaultOptions,
      { body: JSON.stringify(body), method: "DELETE" },
      requestOptions,
    ])
  )
}
