/**
 * API Client Base
 *
 * Core HTTP client with authentication and error handling
 */

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

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

/**
 * Get authentication token — checks localStorage first (remember me), then sessionStorage
 */
export function getAuthToken(): string | null {
  if (typeof window === 'undefined') return null;
  return localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
}

/**
 * Get refresh token — checks localStorage first (remember me), then sessionStorage
 */
export function getRefreshToken(): string | null {
  if (typeof window === 'undefined') return null;
  return localStorage.getItem('refresh_token') || sessionStorage.getItem('refresh_token');
}

/**
 * Set authentication tokens.
 * rememberMe=true → localStorage (persists across browser sessions)
 * rememberMe=false → sessionStorage (cleared when browser closes)
 */
export function setAuthTokens(accessToken: string, refreshToken: string, rememberMe = false): void {
  if (typeof window === 'undefined') return;
  const storage = rememberMe ? localStorage : sessionStorage;
  storage.setItem('access_token', accessToken);
  storage.setItem('refresh_token', refreshToken);
  // Sync to cookie so Next.js middleware can read it for route protection
  document.cookie = `accessToken=${accessToken}; path=/; SameSite=Lax`;
}

/**
 * Clear authentication tokens from both storages
 */
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 middleware cookie
  document.cookie = 'accessToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
}

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

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

/**
 * Refresh access token using refresh token
 */
async function refreshAccessToken(): Promise<string> {
  const refreshToken = getRefreshToken();

  if (!refreshToken) {
    throw new Error('No refresh token available');
  }

  const response = await fetch(`${API_BASE_URL}/api/v1/auth/refresh`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ refreshToken }),
  });

  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 token');
  }

  const data = await response.json();
  const newAccessToken = data.accessToken;

  // Update access token in the same storage that holds the refresh token
  if (typeof window !== 'undefined') {
    const storage = localStorage.getItem('refresh_token') ? localStorage : sessionStorage;
    storage.setItem('access_token', newAccessToken);
    document.cookie = `accessToken=${newAccessToken}; path=/; SameSite=Lax`;
  }

  return newAccessToken;
}

/**
 * Create headers with authentication
 */
export function createHeaders(includeAuth = true): HeadersInit {
  const headers: HeadersInit = {
    'Content-Type': 'application/json',
  };

  if (includeAuth) {
    const token = getAuthToken();
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
  }

  return headers;
}

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

  if (includeAuth) {
    const token = getAuthToken();
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
  }

  return headers;
}

/**
 * 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 token 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,
    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) {
    // Check if we have a refresh token
    const refreshToken = getRefreshToken();
    if (!refreshToken) {
      // No refresh token, redirect to login
      clearAuthTokens();
      if (typeof window !== 'undefined') {
        window.location.href = '/login';
      }
      throw new ApiError('Session expired', 401);
    }

    // Try to refresh the token
    try {
      if (!isRefreshing) {
        isRefreshing = true;
        const newAccessToken = await refreshAccessToken();
        isRefreshing = false;
        onTokenRefreshed(newAccessToken);

        // Retry the original request with new token
        const newHeaders = { ...options.headers };
        if (newHeaders && typeof newHeaders === 'object' && 'Authorization' in newHeaders) {
          (newHeaders as any)['Authorization'] = `Bearer ${newAccessToken}`;
        }

        return apiRequest<T>(
          endpoint,
          {
            ...options,
            headers: newHeaders,
          },
          true
        );
      } else {
        // Already refreshing, wait for the new token
        return new Promise((resolve, reject) => {
          subscribeTokenRefresh(async (newAccessToken: string) => {
            const newHeaders = { ...options.headers };
            if (newHeaders && typeof newHeaders === 'object' && 'Authorization' in newHeaders) {
              (newHeaders as any)['Authorization'] = `Bearer ${newAccessToken}`;
            }

            try {
              const result = await apiRequest<T>(
                endpoint,
                {
                  ...options,
                  headers: newHeaders,
                },
                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',
    headers: createMultipartHeaders(includeAuth),
    body: formData,
  });

  // Handle 401 Unauthorized for file uploads
  if (response.status === 401) {
    const refreshToken = getRefreshToken();
    if (refreshToken) {
      try {
        const newAccessToken = await refreshAccessToken();
        // Retry upload with new token
        const retryResponse = await fetch(url, {
          method: 'POST',
          headers: {
            ...createMultipartHeaders(includeAuth),
            Authorization: `Bearer ${newAccessToken}`,
          },
          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',
    headers: createHeaders(includeAuth),
  });

  // Handle 401 Unauthorized for downloads
  if (response.status === 401) {
    const refreshToken = getRefreshToken();
    if (refreshToken) {
      try {
        const newAccessToken = await refreshAccessToken();
        // Retry download with new token
        const retryResponse = await fetch(url, {
          method: 'GET',
          headers: {
            ...createHeaders(includeAuth),
            Authorization: `Bearer ${newAccessToken}`,
          },
        });

        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);
}
