import { defaultLanguage } from '@/i18n';
import { TokenResponse, PresignUploadRequestDto, PresignUploadResponse, PresignDownloadResponse } from '@/lib/types';

let getTokenFunction: (() => Promise<TokenResponse>) | undefined = undefined;
let baseUrl: string | null = null;

// Add request cache and connection optimization
const requestCache = new Map<string, { data: any; timestamp: number; ttl: number }>();
const CACHE_TTL = 30000; // 30 seconds cache for non-critical requests
const pendingRequests = new Map<string, Promise<any>>();

export function initializeAPI(url: string, getToken: () => Promise<TokenResponse>) {
  getTokenFunction = getToken;
  baseUrl = url;
}

export async function getValidToken(forceRefresh = false): Promise<string> {
  if (!getTokenFunction) {
    throw new Error('API module not initialized. Call initializeAPI(getToken) first.');
  }

  let storedToken = localStorage.getItem('chatbot-token');
  let storedExpiry = localStorage.getItem('chatbot-token-expiry');
  const currentTime = Math.floor(Date.now() / 1000);

  // Add buffer time to prevent token expiry during request
  const bufferTime = 60; // 1 minute buffer
  const isTokenExpiring = storedExpiry && currentTime >= Number(storedExpiry) - bufferTime;

  if (!storedToken || !storedExpiry || isTokenExpiring || forceRefresh) {
    try {
      const tokenResponse = await getTokenFunction();
      storedToken = tokenResponse.token;
      storedExpiry = String(currentTime + (tokenResponse.expiresIn ?? 900));

      localStorage.setItem('chatbot-token', storedToken);
      localStorage.setItem('chatbot-token-expiry', storedExpiry);
    } catch (error) {
      throw error;
    }
  }

  return storedToken;
}

// Optimized fetch with retry logic and connection pooling
async function fetchWithAuth(url: string, options: RequestInit, retry = true): Promise<Response> {
  const headers = new Headers(options.headers);

  try {
    const token = await getValidToken();
    headers.set('Authorization', `Bearer ${token}`);

    // Add performance optimizations
    headers.set('Accept', 'application/json');
    headers.set('Accept-Encoding', 'gzip, deflate, br');
    headers.set('Connection', 'keep-alive');

    options.headers = headers;

    // Add timeout to prevent hanging requests
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout

    try {
      const response = await fetch(url, {
        ...options,
        signal: controller.signal,
        // Add HTTP/2 optimization hints
        cache: 'no-cache',
        mode: 'cors',
      });

      clearTimeout(timeoutId);

      // Handle 401/403 with token refresh
      if ((response.status === 401 || response.status === 403) && retry) {
        const newToken = await getValidToken(true);
        headers.set('Authorization', `Bearer ${newToken}`);
        options.headers = headers;

        // Retry the request with new token
        return fetchWithAuth(url, options, false);
      }

      return response;
    } catch (error) {
      clearTimeout(timeoutId);

      if (error instanceof Error && error.name === 'AbortError') {
        throw new Error('Request timeout - please try again');
      }
      throw error;
    }
  } catch (error) {
    throw error;
  }
}

// Cache management functions
function getCachedResponse(cacheKey: string): any | null {
  const cached = requestCache.get(cacheKey);
  if (cached && Date.now() < cached.timestamp + cached.ttl) {
    return cached.data;
  }

  if (cached) {
    requestCache.delete(cacheKey);
  }

  return null;
}

function setCachedResponse(cacheKey: string, data: any, ttl: number = CACHE_TTL): void {
  requestCache.set(cacheKey, {
    data,
    timestamp: Date.now(),
    ttl,
  });
}

// Deduplicate concurrent requests
function getDuplicateRequest(requestKey: string): Promise<any> | null {
  return pendingRequests.get(requestKey) || null;
}

function setPendingRequest(requestKey: string, promise: Promise<any>): void {
  pendingRequests.set(requestKey, promise);

  // Clean up after request completes
  promise.finally(() => {
    pendingRequests.delete(requestKey);
  });
}

export async function apiRequest(
  endpoint: string,
  method = 'GET',
  body: any = null,
  options: { cache?: boolean; timeout?: number; language?: 'ar' | 'en' }
) {
  if (!baseUrl) throw new Error('API not initialized');

  const url = `${baseUrl}/${endpoint}`;
  const requestKey = `${method}:${endpoint}:${JSON.stringify(body)}`;

  // Check for duplicate in-flight requests (skip for GET: callers receive Response and call .json();
  // sharing the same Response causes "body already consumed" when the second caller parses)
  const duplicateRequest = getDuplicateRequest(requestKey);
  if (duplicateRequest && method !== 'GET') {
    return duplicateRequest;
  }

  // Check cache for GET requests (except real-time endpoints)
  if (method === 'GET' && options.cache !== false && !endpoint.includes('/send-message')) {
    const cached = getCachedResponse(requestKey);
    if (cached) {
      return Promise.resolve(cached);
    }
  }

  const requestOptions: RequestInit = {
    method,
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': method === 'GET' ? 'max-age=30' : 'no-cache',
      'Accept-Language': options.language || defaultLanguage,
    },
    body: body ? JSON.stringify(body) : null,
  };

  const requestPromise = (async () => {
    try {
      const response = await fetchWithAuth(url, requestOptions);

      if (!response.ok) {
        let errorMessage = 'API request failed';

        try {
          // Clone response before reading to avoid consuming the body
          const errorResponse = response.clone();
          const errorData = await errorResponse.json();
          errorMessage = errorData.message || errorData.error || errorMessage;
        } catch (parseError) {
          // If JSON parsing fails, try to get text
          try {
            const errorResponse = response.clone();
            const errorText = await errorResponse.text();
            errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
          } catch {
            errorMessage = `HTTP ${response.status}: ${response.statusText}`;
          }
        }

        throw new Error(errorMessage);
      }

      // Cache successful GET responses
      // Note: We clone before caching to avoid consuming the original response body
      if (method === 'GET' && options.cache !== false) {
        const responseData = response.clone();
        const data = await responseData.json();
        setCachedResponse(requestKey, { json: () => Promise.resolve(data) });
      }

      // Return the original response - it's body hasn't been consumed yet
      // (we only cloned for caching, and the clone was consumed)
      return response;
    } catch (error) {
      throw error;
    }
  })();

  // Track pending request
  setPendingRequest(requestKey, requestPromise);

  return requestPromise;
}

export async function presignUpload(
  chatSessionId: string,
  file: File,
  language: 'ar' | 'en' = defaultLanguage
): Promise<PresignUploadResponse> {
  const requestBody: PresignUploadRequestDto = {
    name: file.name,
    contentType: file.type,
    sizeBytes: file.size,
    pathData: {
      type: 1,
      chatSessionId: chatSessionId,
    },
  };

  const response = await apiRequest('NewFile/presign-upload', 'POST', requestBody, { language });

  if (!response.ok) {
    throw new Error('Failed to get presigned upload URL');
  }

  return await response.json();
}
export async function presignDownload(fileId: string, language: 'ar' | 'en' = defaultLanguage): Promise<PresignDownloadResponse> {
  try {
    const response = await apiRequest(`NewFile/${fileId}/presign-download`, 'GET', null, {
      language,
      cache: false, // Don't cache presigned URLs as they have expiration times
    });

    // If response is not ok, apiRequest would have thrown an error already
    // So we can safely read the JSON here
    try {
      return await response.json();
    } catch (jsonError) {
      // If JSON parsing fails, the response body might have been consumed
      // or the response might not be valid JSON
      if (jsonError instanceof Error) {
        if (jsonError.message.includes('already read')) {
          throw new Error(`Failed to parse response for file ${fileId}: Response body was already consumed`);
        }
        throw new Error(`Failed to parse response for file ${fileId}: ${jsonError.message}`);
      }
      throw new Error(`Failed to parse response for file ${fileId}`);
    }
  } catch (error) {
    // Handle all types of errors
    if (error instanceof Error) {
      // If it's already a descriptive error from apiRequest, re-throw it
      if (error.message.includes('API request failed') || error.message.includes('HTTP')) {
        throw new Error(`Failed to get presigned download URL for file ${fileId}: ${error.message}`);
      }
      // Otherwise, re-throw with context
      throw error;
    }
    // Handle non-Error types
    throw new Error(`Failed to get presigned download URL for file ${fileId}: ${String(error)}`);
  }
}
