import { ssrfSafeFetch } from '@lobechat/ssrf-safe-fetch';

interface UriParserResult {
  base64: string | null;
  mimeType: string | null;
  type: 'url' | 'base64' | null;
}

export const parseDataUri = (dataUri: string): UriParserResult => {
  // Use indexOf instead of regex to avoid stack overflow on large data URIs (e.g. 26MB+ base64 images)
  const DATA_PREFIX = 'data:';
  const BASE64_MARKER = ';base64,';

  if (dataUri.startsWith(DATA_PREFIX)) {
    const markerIndex = dataUri.indexOf(BASE64_MARKER);
    if (markerIndex > DATA_PREFIX.length) {
      const mimeType = dataUri.slice(DATA_PREFIX.length, markerIndex);
      const base64 = dataUri.slice(markerIndex + BASE64_MARKER.length);
      if (base64.length > 0) {
        return { base64, mimeType, type: 'base64' };
      }
    }
  }

  try {
    new URL(dataUri);
    // If it's a valid URL
    return { base64: null, mimeType: null, type: 'url' };
  } catch {
    // Neither a Data URI nor a valid URL
    return { base64: null, mimeType: null, type: null };
  }
};

/**
 * MIME types supported by Google Gemini External URL feature
 * @see https://ai.google.dev/gemini-api/docs/file-input-methods#supported-content-types
 */
const GOOGLE_EXTERNAL_URL_SUPPORTED_TYPES = new Set([
  // Text file types
  'text/html',
  'text/css',
  'text/plain',
  'text/xml',
  'text/csv',
  'text/rtf',
  'text/javascript',
  // Application file types
  'application/json',
  'application/pdf',
  // Image file types
  'image/bmp',
  'image/jpeg',
  'image/png',
  'image/webp',
  // Video file types
  'video/3gpp',
  'video/avi',
  'video/mp4',
  'video/mpeg',
  'video/mpg',
  'video/quicktime',
  'video/webm',
  'video/wmv',
  'video/x-flv',
  // Audio file types
  'audio/aac',
  'audio/aiff',
  'audio/flac',
  'audio/mp3',
  'audio/ogg',
  'audio/wav',
]);

const normalizeExternalContentType = (contentType: string): string => {
  // Some servers return non-standard alias `image/jpg` for JPEG files.
  // Normalize it to the standard type to avoid unnecessary fallback to inline base64.
  if (contentType === 'image/jpg') return 'image/jpeg';

  // MP3 is commonly served as `audio/mpeg` / `audio/mpg`, but Gemini's external
  // URL feature expects `audio/mp3`. Normalize so audio URLs are handed off as
  // fileData instead of falling back to downloading + inlining the whole file.
  if (contentType === 'audio/mpeg' || contentType === 'audio/mpg') return 'audio/mp3';

  return contentType;
};

const parseContentLength = (contentLength: string | null): number | null => {
  const normalized = contentLength?.trim();
  if (!normalized || !/^\d+$/.test(normalized)) return null;

  const value = Number(normalized);
  return Number.isSafeInteger(value) ? value : null;
};

/**
 * Maximum file size limits for Google Gemini file input
 * @see https://ai.google.dev/gemini-api/docs/file-input-methods#method-comparison
 *
 * External URLs: 100MB for all file types
 * Inline data: 100MB general, 50MB for PDFs
 */
const MAX_EXTERNAL_URL_SIZE = 100 * 1024 * 1024; // 100MB for external URLs (all types)
const MAX_INLINE_DATA_SIZE = 100 * 1024 * 1024; // 100MB for inline data (general)
const MAX_INLINE_PDF_SIZE = 50 * 1024 * 1024; // 50MB for inline PDFs only

export { MAX_INLINE_DATA_SIZE, MAX_INLINE_PDF_SIZE };

export interface ExternalUrlValidation {
  /** Content-Length from response headers */
  contentLength: number;
  /** Content-Type from response headers */
  contentType: string;
  /** Whether the URL was rejected due to size limit */
  isTooLarge?: boolean;
  /** Whether the URL is valid for external URL usage */
  isValid: boolean;
  /** Reason for invalid URL */
  reason?: string;
}

/**
 * Check if a URL is an external HTTP(S) URL
 * SSRF protection is enforced by ssrfSafeFetch during validation
 */
export const isPublicExternalUrl = (url: string): boolean => {
  try {
    const parsed = new URL(url);

    // Only allow http and https protocols
    if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
      return false;
    }

    return true;
  } catch {
    return false;
  }
};

/**
 * Validate an external URL for Google Gemini file input
 * Performs a HEAD request to check Content-Length and Content-Type
 *
 * @param url - The URL to validate
 * @returns Validation result with content info
 */
export const validateExternalUrl = async (url: string): Promise<ExternalUrlValidation> => {
  try {
    // Perform HEAD request to get headers without downloading the file
    const res = await ssrfSafeFetch(
      url,
      {
        headers: {
          'User-Agent': 'LobeChat/1.0 (https://lobehub.com)',
        },
        method: 'HEAD',
      },
      {
        allowIPAddressList: [],
        allowPrivateIPAddress: false,
      },
    );

    if (!res.ok) {
      return {
        contentLength: 0,
        contentType: '',
        isValid: false,
        reason: `HTTP ${res.status}: ${res.statusText}`,
      };
    }

    const contentLength = parseContentLength(res.headers.get('content-length'));
    const contentType = normalizeExternalContentType(
      (res.headers.get('content-type') || '').split(';')[0].trim().toLowerCase(),
    );

    // Check MIME type support
    if (!GOOGLE_EXTERNAL_URL_SUPPORTED_TYPES.has(contentType)) {
      return {
        contentLength: contentLength || 0,
        contentType,
        isValid: false,
        reason: `Unsupported content type: ${contentType}`,
      };
    }

    if (contentLength === null) {
      return {
        contentLength: 0,
        contentType,
        isValid: false,
        reason: 'Missing or invalid Content-Length header',
      };
    }

    // Check file size - External URLs support 100MB for all file types
    // (Unlike inline data where PDFs are limited to 50MB)
    if (contentLength > MAX_EXTERNAL_URL_SIZE) {
      return {
        contentLength,
        contentType,
        isTooLarge: true,
        isValid: false,
        reason: `File too large: ${contentLength} bytes (max ${MAX_EXTERNAL_URL_SIZE} bytes)`,
      };
    }

    return {
      contentLength,
      contentType,
      isValid: true,
    };
  } catch (error) {
    return {
      contentLength: 0,
      contentType: '',
      isValid: false,
      reason: `Failed to validate URL: ${error instanceof Error ? error.message : String(error)}`,
    };
  }
};
