export interface ServiceRequestOptions {
  simulateError?: boolean
  latencyMs?: number
}

const toNumber = (raw: string | undefined, fallback: number): number => {
  if (!raw) {
    return fallback
  }

  const parsed = Number(raw)
  return Number.isFinite(parsed) ? parsed : fallback
}

const DEFAULT_LATENCY_MS = toNumber(import.meta.env.VITE_MOCK_API_LATENCY_MS, 260)
const MOCK_FORCE_ERROR = import.meta.env.VITE_MOCK_FORCE_ERROR === 'true'
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? ''

const deepClone = <T>(payload: T): T => {
  if (typeof structuredClone === 'function') {
    return structuredClone(payload)
  }

  return JSON.parse(JSON.stringify(payload)) as T
}

const wait = async (ms: number): Promise<void> =>
  new Promise((resolve) => {
    window.setTimeout(resolve, ms)
  })

const toApiError = (status: number, detail: string): Error => {
  if (detail) {
    return new Error(detail)
  }

  return new Error(`Request failed with status ${status}`)
}

const toURL = (path: string): string => {
  const normalizedBase = API_BASE_URL.replace(/\/$/, '')
  const normalizedPath = path.startsWith('/') ? path : `/${path}`

  return `${normalizedBase}${normalizedPath}`
}

export const isMockApiEnabled = (): boolean => import.meta.env.VITE_ENABLE_MOCK_API !== 'false'

const apiRequest = async <T>(
  path: string,
  init: RequestInit,
  queryParams: Record<string, string | number | undefined> = {},
): Promise<T> => {
  const url = new URL(toURL(path), window.location.origin)

  for (const [key, value] of Object.entries(queryParams)) {
    if (value === undefined || value === '') {
      continue
    }

    url.searchParams.set(key, String(value))
  }

  const response = await fetch(url.toString(), init)

  if (!response.ok) {
    let message = ''

    try {
      const body = (await response.json()) as { error?: string }
      message = body.error ?? ''
    } catch {
      message = ''
    }

    throw toApiError(response.status, message)
  }

  return (await response.json()) as T
}

export const apiGet = async <T>(
  path: string,
  queryParams: Record<string, string | number | undefined> = {},
  headers: Record<string, string> = {},
): Promise<T> =>
  apiRequest<T>(
    path,
    {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        ...headers,
      },
    },
    queryParams,
  )

export const apiPost = async <T>(
  path: string,
  body: unknown,
  headers: Record<string, string> = {},
): Promise<T> =>
  apiRequest<T>(path, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      ...headers,
    },
    body: JSON.stringify(body),
  })

export const apiDelete = async <T>(
  path: string,
  headers: Record<string, string> = {},
): Promise<T> =>
  apiRequest<T>(path, {
    method: 'DELETE',
    headers: {
      Accept: 'application/json',
      ...headers,
    },
  })

export const mockResponse = async <T>(
  requestName: string,
  producer: () => T,
  options: ServiceRequestOptions = {},
): Promise<T> => {
  const latency = options.latencyMs ?? DEFAULT_LATENCY_MS
  await wait(Math.max(0, latency))

  if (!isMockApiEnabled()) {
    throw new Error(`Mock API is disabled for ${requestName}`)
  }

  if (MOCK_FORCE_ERROR || options.simulateError) {
    throw new Error(`Mock request failed for ${requestName}`)
  }

  return deepClone(producer())
}
