/**
 * Asset URL Resolution Utility
 *
 * Resolves relative asset paths to absolute backend URLs for playback and preloading.
 * Supports presigned S3 URLs for direct, fast asset downloads.
 */

import axios, { AxiosError } from 'axios';
import { baseURLApi } from '../config';
import { logger } from './logger';

/**
 * Check if a URL is a presigned S3 URL
 */
const isPresignedS3Url = (url: string): boolean => {
  return url.includes('X-Amz-Signature=') || url.includes('x-amz-signature=');
};

/**
 * Setup Axios interceptor to detect presigned URL failures.
 * Called once during app initialization.
 */
export const setupPresignedUrlInterceptor = (): void => {
  axios.interceptors.response.use(
    (response) => response,
    (error: AxiosError) => {
      const url = error.config?.url || '';

      // Check if this is a presigned S3 URL failure (likely CORS)
      if (isPresignedS3Url(url) && !presignedUrlsDisabled) {
        logger.info('Presigned URL request failed, disabling presigned URLs');
        disablePresignedUrls();
      }

      return Promise.reject(error);
    },
  );
};

/**
 * Disable presigned URLs globally.
 * Called when we detect S3 CORS is not configured.
 */
export const disablePresignedUrls = (): void => {
  if (!presignedUrlsDisabled) {
    presignedUrlsDisabled = true;
    presignedUrlCache.clear();
    logger.info('Presigned URLs disabled - all requests will use proxy');
  }
};

interface PresignedUrlCache {
  url: string;
  expiresAt: number;
}

// In-memory cache for presigned URLs
const presignedUrlCache = new Map<string, PresignedUrlCache>();
const CACHE_BUFFER_MS = 5 * 60 * 1000; // Refresh 5 min before expiry
const PRESIGN_TTL_MS = 60 * 60 * 1000; // 1 hour TTL

// Global flag to disable presigned URLs when CORS is not configured
// Once set to true, all URL resolutions will use proxy URLs
let presignedUrlsDisabled = false;

// Flag to track if presigned URLs have been verified to work
// Until verified, resolveAssetPlaybackUrl will use proxy URLs
let presignedUrlsVerified = false;

// Batch queue for presigned URL requests
let pendingBatch: string[] = [];
let batchPromiseResolvers: Array<{
  resolve: (value: Record<string, string>) => void;
  reject: (reason?: unknown) => void;
}> = [];
let batchTimeout: ReturnType<typeof setTimeout> | null = null;
const BATCH_DELAY_MS = 10; // Small delay to batch concurrent requests

/**
 * Fetch presigned URLs for a batch of storage keys
 */
const fetchPresignedUrlsBatch = async (
  urls: string[],
): Promise<Record<string, string>> => {
  // Use explicit baseURLApi to avoid double /api/ prefix issues
  const response = await axios.post<{ presignedUrls: Record<string, string> }>(
    `${baseURLApi}/file/presign`,
    { urls },
    { baseURL: '' }, // Override baseURL to prevent duplication
  );

  // Cache the results
  const now = Date.now();
  Object.entries(response.data.presignedUrls).forEach(([key, url]) => {
    presignedUrlCache.set(key, {
      url,
      expiresAt: now + PRESIGN_TTL_MS,
    });
  });

  return response.data.presignedUrls;
};

/**
 * Process the pending batch of URLs
 */
const processBatch = async (): Promise<void> => {
  const urls = Array.from(new Set(pendingBatch)); // Deduplicate
  const resolvers = [...batchPromiseResolvers];
  pendingBatch = [];
  batchPromiseResolvers = [];
  batchTimeout = null;

  if (urls.length === 0) {
    resolvers.forEach(({ resolve }) => resolve({}));
    return;
  }

  try {
    const result = await fetchPresignedUrlsBatch(urls);
    resolvers.forEach(({ resolve }) => resolve(result));
  } catch (error) {
    resolvers.forEach(({ reject }) => reject(error));
  }
};

/**
 * Queue a URL for batch presigning and return a promise that resolves when the batch completes.
 * Used by the preloader to efficiently fetch presigned URLs for multiple assets.
 */
export const queuePresignedUrl = (
  storageKey: string,
): Promise<string | null> => {
  // Check cache first
  const cached = presignedUrlCache.get(storageKey);
  if (cached && cached.expiresAt > Date.now() + CACHE_BUFFER_MS) {
    return Promise.resolve(cached.url);
  }

  // Add to pending batch
  if (!pendingBatch.includes(storageKey)) {
    pendingBatch.push(storageKey);
  }

  // Return a promise that will be resolved when the batch is processed
  return new Promise((resolve, reject) => {
    batchPromiseResolvers.push({
      resolve: (result) => resolve(result[storageKey] || null),
      reject,
    });

    // Schedule batch processing if not already scheduled
    if (!batchTimeout) {
      batchTimeout = setTimeout(processBatch, BATCH_DELAY_MS);
    }
  });
};

/**
 * Queue multiple URLs for batch presigning.
 * More efficient than calling queuePresignedUrl multiple times.
 * Returns empty object if presigned URLs are disabled.
 */
export const queuePresignedUrls = (
  storageKeys: string[],
): Promise<Record<string, string>> => {
  // Skip if presigned URLs are disabled
  if (presignedUrlsDisabled) {
    return Promise.resolve({});
  }

  // Filter out already cached URLs
  const uncachedKeys = storageKeys.filter((key) => {
    const cached = presignedUrlCache.get(key);
    return !cached || cached.expiresAt <= Date.now() + CACHE_BUFFER_MS;
  });

  if (uncachedKeys.length === 0) {
    // All URLs are cached, return from cache
    const result: Record<string, string> = {};
    storageKeys.forEach((key) => {
      const cached = presignedUrlCache.get(key);
      if (cached) {
        result[key] = cached.url;
      }
    });
    return Promise.resolve(result);
  }

  // Add uncached keys to pending batch
  uncachedKeys.forEach((key) => {
    if (!pendingBatch.includes(key)) {
      pendingBatch.push(key);
    }
  });

  // Return a promise that will be resolved when the batch is processed
  return new Promise((resolve, reject) => {
    batchPromiseResolvers.push({
      resolve: (result) => {
        // Include cached URLs in the result
        const finalResult: Record<string, string> = {};
        storageKeys.forEach((key) => {
          if (result[key]) {
            finalResult[key] = result[key];
          } else {
            const cached = presignedUrlCache.get(key);
            if (cached) {
              finalResult[key] = cached.url;
            }
          }
        });
        resolve(finalResult);
      },
      reject,
    });

    // Schedule batch processing if not already scheduled
    if (!batchTimeout) {
      batchTimeout = setTimeout(processBatch, BATCH_DELAY_MS);
    }
  });
};

/**
 * Flush the pending batch immediately and fetch presigned URLs.
 * Call this before starting asset preloading to ensure all URLs are ready.
 */
export const flushPresignedUrlQueue = async (): Promise<void> => {
  if (batchTimeout) {
    clearTimeout(batchTimeout);
    batchTimeout = null;
  }
  await processBatch();
};

/**
 * Get a presigned URL from cache (synchronous).
 * Returns null if not cached, expired, disabled, or not yet verified.
 */
export const getPresignedUrl = (storageKey: string): string | null => {
  // Return null if presigned URLs are disabled or not verified yet
  if (presignedUrlsDisabled || !presignedUrlsVerified) {
    return null;
  }
  const cached = presignedUrlCache.get(storageKey);
  if (cached && cached.expiresAt > Date.now()) {
    return cached.url;
  }
  return null;
};

/**
 * Check if presigned URLs are currently disabled.
 */
export const arePresignedUrlsDisabled = (): boolean => {
  return presignedUrlsDisabled;
};

/**
 * Mark presigned URLs as verified (they work).
 * Called by preloader after a successful presigned URL fetch.
 */
export const markPresignedUrlsVerified = (): void => {
  if (!presignedUrlsDisabled && !presignedUrlsVerified) {
    presignedUrlsVerified = true;
    logger.info('Presigned URLs verified - enabling direct S3 access');
  }
};

/**
 * Mark a presigned URL as failed (removes from cache).
 * Call this when a fetch using the presigned URL fails (e.g., CORS error).
 * This allows resolveAssetPlaybackUrl to fall back to the proxy URL.
 * Also disables presigned URLs globally for this session.
 */
export const markPresignedUrlFailed = (storageKey: string): void => {
  presignedUrlCache.delete(storageKey);
  disablePresignedUrls();
};

/**
 * Check if a path is a relative storage path (not a full URL or special protocol)
 */
export const isRelativeStoragePath = (url: string): boolean => {
  const normalized = url.trim();
  return (
    normalized.length > 0 &&
    !normalized.startsWith('http://') &&
    !normalized.startsWith('https://') &&
    !normalized.startsWith('data:') &&
    !normalized.startsWith('blob:') &&
    !normalized.startsWith('/api/file/download') &&
    !normalized.startsWith('/file/download')
  );
};

/**
 * Extract relative storage path from a full S3 URL.
 * Converts: https://bucket.s3.region.amazonaws.com/prefix/assets/projectId/file.ext
 * To: assets/projectId/file.ext
 * Returns original if already a relative path or not an S3 URL.
 */
export const extractStoragePath = (url: string): string => {
  const normalized = url?.trim() || '';
  if (!normalized) return '';
  if (isRelativeStoragePath(normalized)) return normalized;

  // Extract path starting from 'assets/'
  const s3Match = normalized.match(
    /^https?:\/\/[^/]+\.s3\.[^/]+\.amazonaws\.com\/[^/]+\/(assets\/.+)$/,
  );
  return s3Match ? s3Match[1] : normalized;
};

/**
 * Resolves an asset path to its full playback URL.
 *
 * Handles:
 * - data: and blob: URLs (passthrough)
 * - /api/file/download URLs (passthrough)
 * - /file/download URLs (prepend baseURLApi)
 * - Full http/https URLs (passthrough)
 * - Relative paths (check presigned cache, fallback to /api/file/download?privateUrl=...)
 *
 * @param value - The asset URL or path to resolve
 * @returns The resolved full URL, or empty string if no value
 */
export const resolveAssetPlaybackUrl = (value?: string): string => {
  const normalized = String(value || '').trim();
  if (!normalized) return '';

  // Data and blob URLs pass through
  if (normalized.startsWith('data:') || normalized.startsWith('blob:'))
    return normalized;

  // Already an API file download URL
  if (normalized.startsWith('/api/file/download')) return normalized;

  // File download path (prepend API base)
  if (normalized.startsWith('/file/download'))
    return `${baseURLApi}${normalized}`;

  // Full URLs pass through
  if (normalized.startsWith('http://') || normalized.startsWith('https://'))
    return normalized;

  // Relative path - check presigned URL cache first
  const presigned = getPresignedUrl(normalized);
  if (presigned) {
    return presigned;
  }

  // Fallback to backend proxy
  const normalizedPrivateUrl = normalized.replace(/^\/+/, '');
  return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(normalizedPrivateUrl)}`;
};

/**
 * Clear the presigned URL cache.
 * Useful when user logs out or storage provider changes.
 */
export const clearPresignedUrlCache = (): void => {
  presignedUrlCache.clear();
};
