import { getAuthHeaders, refreshToken, logout } from './auth';

const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/v1';

interface FetchOptions extends Omit<RequestInit, 'body'> {
  body?: unknown;
}

export class ApiError extends Error {
  constructor(
    message: string,
    public status: number,
    public data?: unknown,
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

async function handleResponse<T>(response: Response): Promise<T> {
  if (!response.ok) {
    const data: Record<string, unknown> = await (response.json() as Promise<Record<string, unknown>>).catch(() => ({}));
    throw new ApiError(
      (typeof data.message === 'string' ? data.message : null) ?? `Ошибка запроса: ${response.status}`,
      response.status,
      data,
    );
  }

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

  return response.json() as Promise<T>;
}

export async function fetchWithAuth<T>(
  url: string,
  options: FetchOptions = {},
): Promise<T> {
  const { body, headers: customHeaders, ...rest } = options;

  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...getAuthHeaders(),
    ...(customHeaders as Record<string, string>),
  };

  const config: RequestInit = {
    ...rest,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  };

  let response = await fetch(`${BASE_URL}${url}`, config);

  if (response.status === 401) {
    try {
      const newToken = await refreshToken();
      headers['Authorization'] = `Bearer ${newToken}`;
      response = await fetch(`${BASE_URL}${url}`, { ...config, headers });
    } catch {
      logout();
      throw new ApiError('Сессия истекла', 401);
    }
  }

  return handleResponse<T>(response);
}

export const api = {
  get<T>(url: string, options?: FetchOptions): Promise<T> {
    return fetchWithAuth<T>(url, { ...options, method: 'GET' });
  },

  post<T>(url: string, body?: unknown, options?: FetchOptions): Promise<T> {
    return fetchWithAuth<T>(url, { ...options, method: 'POST', body });
  },

  patch<T>(url: string, body?: unknown, options?: FetchOptions): Promise<T> {
    return fetchWithAuth<T>(url, { ...options, method: 'PATCH', body });
  },

  delete<T>(url: string, options?: FetchOptions): Promise<T> {
    return fetchWithAuth<T>(url, { ...options, method: 'DELETE' });
  },
};
