import { Config } from './config';

interface TokenCache {
  accessToken: string;
  expiresAt: number;
}

let tokenCache: TokenCache | null = null;

interface TokenResponse {
  access_token: string;
  expires_in: number;
  token_type: string;
}

async function fetchToken(config: Config): Promise<string> {
  const now = Date.now();
  if (tokenCache !== null && tokenCache.expiresAt > now + 30_000) {
    return tokenCache.accessToken;
  }

  const body = new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: config.clientId,
    client_secret: config.clientSecret,
  });

  const res = await fetch(`${config.apiUrl}/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  });

  if (!res.ok) {
    const text = await res.text();
    throw new Error(`Authentication failed (${res.status}): ${text}`);
  }

  const data = (await res.json()) as TokenResponse;
  tokenCache = {
    accessToken: data.access_token,
    expiresAt: now + data.expires_in * 1000,
  };
  return tokenCache.accessToken;
}

export function clearTokenCache(): void {
  tokenCache = null;
}

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

interface ApiRequestOptions {
  method?: HttpMethod;
  body?: unknown;
  params?: Record<string, string>;
}

export async function apiRequest<T>(
  config: Config,
  endpoint: string,
  options: ApiRequestOptions = {},
): Promise<T> {
  const token = await fetchToken(config);
  const { method = 'GET', body, params } = options;

  let url = `${config.apiUrl}${endpoint}`;
  if (params !== undefined && Object.keys(params).length > 0) {
    const qs = new URLSearchParams(params);
    url = `${url}?${qs.toString()}`;
  }

  const headers: Record<string, string> = {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  };

  const fetchOptions: RequestInit = { method, headers };
  if (body !== undefined) {
    fetchOptions.body = JSON.stringify(body);
  }

  const res = await fetch(url, fetchOptions);

  if (!res.ok) {
    const text = await res.text();
    throw new Error(`API error (${res.status}): ${text}`);
  }

  if (res.status === 204) {
    return undefined as unknown as T;
  }

  return (await res.json()) as T;
}
