/**
 * API Client Base
 *
 * Core HTTP client with authentication and error handling.
 *
 * Authentication relies on httpOnly cookies set by the backend
 * (`accessToken` / `refreshToken`), so no token is ever stored in
 * localStorage or readable from JavaScript (XSS mitigation).
 * The non-httpOnly `xpeditis_session` flag cookie (which contains no
 * token) tells the frontend whether a session exists.
 */

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000';

const SESSION_FLAG_COOKIE = 'xpeditis_session';

// Track if we're currently refreshing to avoid multiple simultaneous refresh requests
let isRefreshing = false;
let refreshSubscribers: Array<() => void> = [];

/**
 * Whether an authenticated session exists (based on the readable flag cookie
 * set by the backend alongside the httpOnly token cookies).
 */
export function hasSession(): boolean {
  if (typeof document === 'undefined') return false;
  return document.cookie.split('; ').some(cookie => cookie.startsWith(`${SESSION_FLAG_COOKIE}=`));
}

/**
 * Clear client-side auth state.
 * Token cookies are httpOnly and are cleared by the backend on logout;
 * this removes the user cache and any tokens left over from the legacy
 * localStorage-based auth.
 */
export function clearAuthTokens(): void {
  if (typeof window === 'undefined') return;
  localStorage.removeItem('access_token');
  localStorage.removeItem('refresh_token');
  localStorage.removeItem('user');
  sessionStorage.removeItem('access_token');
  sessionStorage.removeItem('refresh_token');
  sessionStorage.removeItem('user');
  // Expire the legacy middleware cookie and the session flag (best effort —
  // the backend clears the authoritative httpOnly cookies)
  document.cookie = 'accessToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
  document.cookie = `${SESSION_FLAG_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
}

/**
 * Add subscriber to be notified when the session is refreshed
 */
function subscribeTokenRefresh(callback: () => void): void {
  refreshSubscribers.push(callback);
}

/**
 * Notify all subscribers that the session has been refreshed
 */
function onTokenRefreshed(): void {
  refreshSubscribers.forEach(callback => callback());
  refreshSubscribers = [];
}

/**
 * Refresh the session — the backend reads the httpOnly refresh cookie and
 * sets new token cookies on success.
 */
async function refreshSession(): Promise<void> {
  const response = await fetch(`${API_BASE_URL}/api/v1/auth/refresh`, {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
  });

  if (!response.ok) {
    // Refresh token invalid or expired, clear everything
    clearAuthTokens();
    // Redirect to login
    if (typeof window !== 'undefined') {
      window.location.href = '/login';
    }
    throw new Error('Failed to refresh session');
  }
}

/**
 * Create headers (auth is carried by httpOnly cookies, not headers)
 */
export function createHeaders(_includeAuth = true): HeadersInit {
  return {
    'Content-Type': 'application/json',
  };
}

/**
 * Create headers for multipart form data
 */
export function createMultipartHeaders(_includeAuth = true): HeadersInit {
  return {};
}

/**
 * API Error
 */
export class ApiError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public response?: any
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

/**
 * Make API request with automatic session refresh on 401
 */
export async function apiRequest<T>(
  endpoint: string,
  options: RequestInit = {},
  isRetry = false
): Promise<T> {
  const url = `${API_BASE_URL}${endpoint}`;

  const response = await fetch(url, {
    ...options,
    credentials: 'include',
    headers: {
      ...options.headers,
    },
  });

  // Handle 401 Unauthorized - token expired
  // Skip auto-redirect for auth endpoints (login, register, refresh) - they handle their own errors
  const isAuthEndpoint =
    endpoint.includes('/auth/login') ||
    endpoint.includes('/auth/register') ||
    endpoint.includes('/auth/refresh');

  if (response.status === 401 && !isRetry && !isAuthEndpoint) {
    if (!hasSession()) {
      // No session, redirect to login
      clearAuthTokens();
      if (typeof window !== 'undefined') {
        window.location.href = '/login';
      }
      throw new ApiError('Session expired', 401);
    }

    // Try to refresh the session (cookies are updated server-side)
    try {
      if (!isRefreshing) {
        isRefreshing = true;
        await refreshSession();
        isRefreshing = false;
        onTokenRefreshed();

        return apiRequest<T>(endpoint, options, true);
      } else {
        // Already refreshing, wait for it to complete
        return new Promise((resolve, reject) => {
          subscribeTokenRefresh(async () => {
            try {
              const result = await apiRequest<T>(endpoint, options, true);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });
      }
    } catch (refreshError) {
      isRefreshing = false;
      refreshSubscribers = [];
      throw refreshError;
    }
  }

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new ApiError(
      error.message || `API request failed: ${response.statusText}`,
      response.status,
      error
    );
  }

  // Handle 204 No Content
  if (response.status === 204) {
    return undefined as T;
  }

  return response.json();
}

/**
 * GET request
 */
export async function get<T>(endpoint: string, includeAuth = true): Promise<T> {
  return apiRequest<T>(endpoint, {
    method: 'GET',
    headers: createHeaders(includeAuth),
  });
}

/**
 * POST request
 */
export async function post<T>(endpoint: string, data?: any, includeAuth = true): Promise<T> {
  return apiRequest<T>(endpoint, {
    method: 'POST',
    headers: createHeaders(includeAuth),
    body: data ? JSON.stringify(data) : undefined,
  });
}

/**
 * PATCH request
 */
export async function patch<T>(endpoint: string, data: any, includeAuth = true): Promise<T> {
  return apiRequest<T>(endpoint, {
    method: 'PATCH',
    headers: createHeaders(includeAuth),
    body: JSON.stringify(data),
  });
}

/**
 * DELETE request
 */
export async function del<T>(endpoint: string, includeAuth = true): Promise<T> {
  return apiRequest<T>(endpoint, {
    method: 'DELETE',
    headers: createHeaders(includeAuth),
  });
}

/**
 * Upload file (multipart/form-data)
 */
export async function upload<T>(
  endpoint: string,
  formData: FormData,
  includeAuth = true
): Promise<T> {
  const url = `${API_BASE_URL}${endpoint}`;

  const response = await fetch(url, {
    method: 'POST',
    credentials: 'include',
    headers: createMultipartHeaders(includeAuth),
    body: formData,
  });

  // Handle 401 Unauthorized for file uploads
  if (response.status === 401 && hasSession()) {
    try {
      await refreshSession();
      // Retry upload with refreshed cookies
      const retryResponse = await fetch(url, {
        method: 'POST',
        credentials: 'include',
        headers: createMultipartHeaders(includeAuth),
        body: formData,
      });

      if (!retryResponse.ok) {
        const error = await retryResponse.json().catch(() => ({}));
        throw new ApiError(
          error.message || `Upload failed: ${retryResponse.statusText}`,
          retryResponse.status,
          error
        );
      }

      return retryResponse.json();
    } catch (refreshError) {
      clearAuthTokens();
      if (typeof window !== 'undefined') {
        window.location.href = '/login';
      }
      throw refreshError;
    }
  }

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new ApiError(
      error.message || `Upload failed: ${response.statusText}`,
      response.status,
      error
    );
  }

  return response.json();
}

/**
 * Download file
 */
export async function download(
  endpoint: string,
  filename: string,
  includeAuth = true
): Promise<void> {
  const url = `${API_BASE_URL}${endpoint}`;

  const response = await fetch(url, {
    method: 'GET',
    credentials: 'include',
    headers: createHeaders(includeAuth),
  });

  // Handle 401 Unauthorized for downloads
  if (response.status === 401 && hasSession()) {
    try {
      await refreshSession();
      // Retry download with refreshed cookies
      const retryResponse = await fetch(url, {
        method: 'GET',
        credentials: 'include',
        headers: createHeaders(includeAuth),
      });

      if (!retryResponse.ok) {
        throw new ApiError(`Download failed: ${retryResponse.statusText}`, retryResponse.status);
      }

      const blob = await retryResponse.blob();
      const downloadUrl = window.URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = downloadUrl;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      window.URL.revokeObjectURL(downloadUrl);
      return;
    } catch (refreshError) {
      clearAuthTokens();
      if (typeof window !== 'undefined') {
        window.location.href = '/login';
      }
      throw refreshError;
    }
  }

  if (!response.ok) {
    throw new ApiError(`Download failed: ${response.statusText}`, response.status);
  }

  const blob = await response.blob();
  const downloadUrl = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = downloadUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  window.URL.revokeObjectURL(downloadUrl);
}
