import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApiBaseUrl } from '@/src/utils/config';
import { ApiError } from '@/src/utils/apiError';
import type { TokenResponse } from '@/src/types/auth';

const ACCESS_KEY = 'dailymeals.access';
const REFRESH_KEY = 'dailymeals.refresh';

let accessToken: string | null = null;
let refreshToken: string | null = null;
let refreshPromise: Promise<string | null> | null = null;

export async function loadStoredTokens(): Promise<void> {
  accessToken = await AsyncStorage.getItem(ACCESS_KEY);
  refreshToken = await AsyncStorage.getItem(REFRESH_KEY);
}

export async function saveTokens(tokens: TokenResponse): Promise<void> {
  accessToken = tokens.accessToken;
  refreshToken = tokens.refreshToken;
  await AsyncStorage.multiSet([
    [ACCESS_KEY, tokens.accessToken],
    [REFRESH_KEY, tokens.refreshToken],
  ]);
}

export async function clearTokens(): Promise<void> {
  accessToken = null;
  refreshToken = null;
  await AsyncStorage.multiRemove([ACCESS_KEY, REFRESH_KEY]);
}

export function getAccessToken(): string | null {
  return accessToken;
}

export async function getRefreshToken(): Promise<string | null> {
  if (refreshToken) return refreshToken;
  return AsyncStorage.getItem(REFRESH_KEY);
}

async function refreshAccessToken(): Promise<string | null> {
  const storedRefresh = refreshToken ?? (await AsyncStorage.getItem(REFRESH_KEY));
  if (!storedRefresh) return null;
  refreshToken = storedRefresh;
  const res = await fetch(`${getApiBaseUrl()}/auth/refresh`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken: storedRefresh }),
  });
  if (!res.ok) return null;
  const tokens = (await res.json()) as TokenResponse;
  await saveTokens(tokens);
  return tokens.accessToken;
}

async function performRefresh(): Promise<string | null> {
  if (!refreshPromise) {
    refreshPromise = refreshAccessToken().finally(() => {
      refreshPromise = null;
    });
  }
  return refreshPromise;
}

interface ApiFetchOptions extends Omit<RequestInit, 'body'> {
  body?: unknown;
  skipAuth?: boolean;
  retried?: boolean;
}

export async function apiFetch<T>(path: string, options: ApiFetchOptions = {}): Promise<T> {
  const { body, skipAuth, retried, headers, ...rest } = options;
  const url = `${getApiBaseUrl()}${path.startsWith('/') ? path : `/${path}`}`;

  const requestHeaders: Record<string, string> = {
    Accept: 'application/json',
    ...(headers as Record<string, string>),
  };

  if (!(body instanceof FormData)) {
    requestHeaders['Content-Type'] = 'application/json';
  }

  if (!skipAuth && accessToken) {
    requestHeaders.Authorization = `Bearer ${accessToken}`;
  }

  const res = await fetch(url, {
    ...rest,
    headers: requestHeaders,
    body: body instanceof FormData ? body : body !== undefined ? JSON.stringify(body) : undefined,
  });

  const isAuthPath =
    path.includes('/auth/login') ||
    path.includes('/auth/register') ||
    path.includes('/auth/refresh');

  if (res.status === 401 && !skipAuth && !retried && !isAuthPath) {
    const newToken = await performRefresh();
    if (newToken) {
      return apiFetch<T>(path, { ...options, retried: true });
    }
    await clearTokens();
    throw new ApiError('Session expired.', 401);
  }

  if (!res.ok) {
    let message = `Request failed (${res.status})`;
    try {
      const data = (await res.json()) as { error?: string; title?: string };
      const genericTitles = new Set(['An unexpected error occurred.', 'Internal Server Error']);
      if (data.error) {
        message = data.error;
      } else if (data.title && !genericTitles.has(data.title)) {
        message = data.title;
      }
    } catch {
      // ignore parse errors
    }
    throw new ApiError(message, res.status);
  }

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

  const contentType = res.headers.get('content-type') ?? '';
  if (contentType.includes('application/json')) {
    return (await res.json()) as T;
  }

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