import { CodedError } from '@carnesen/coded-error';
import type { ReadableStream as NodeWebReadableStream } from 'node:stream/web';
import { logger } from './logger';
import { Readable } from 'node:stream';

export async function throwIfNotOk(response: Response) {
  if (!response.ok) {
    logger.error(`Status: ${response.status}, ${response.statusText}`);
    const message = `Server responded ${response.status} ("${response.statusText}")`;
    const code = response.status;
    throw new CodedError(message, code);
  }
}

export async function fetchWithTimeout(
  url: string,
  options = {},
  timeout = 6000
) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    clearTimeout(id);
    await throwIfNotOk(response);
    if (response.body === null) {
      throw new Error(`Body empty for query ${url}`);
    }
    // Typescript matches the wrong type to body (DOM) so cast body to desired type
    return response as Response & { body: NodeWebReadableStream };
  } catch (error) {
    clearTimeout(id);

    if (error.name === 'AbortError') {
      throw new Error('Request timed out');
    } else {
      throw error;
    }
  }
}

export async function downloadJson(url: string) {
  const options = { method: 'Get' };
  const response = await fetchWithTimeout(url, options);
  await throwIfNotOk(response);
  return response.json();
}

export async function fetchFilestream(url: string, options: any = {}) {
  const response = await fetchWithTimeout(url, {
    ...options
  });
  await throwIfNotOk(response);
  if (response.body === null) {
    throw new Error(`Body empty for query ${url}`);
  }
  // Typescript matches the wrong type to body (DOM) so cast body to desired type
  return Readable.fromWeb(response.body as NodeWebReadableStream);
}
