'use client';

export interface UploadedMedia {
  id: string;
  url: string;
  altText?: string | null;
  mimeType?: string | null;
}

export class MediaUploadError extends Error {
  constructor(
    message: string,
    readonly code?: string,
    readonly status?: number,
  ) {
    super(message);
    this.name = 'MediaUploadError';
  }
}

interface JsonResponse extends Record<string, unknown> {
  id?: string;
  uploadUrl?: string;
  requiredHeaders?: Record<string, string>;
  media?: UploadedMedia;
  error?: string;
  code?: string;
  provider?: string | null;
  stuffboxUpdatedAt?: string | null;
}

export type StorageProvider = 'stuffbox' | null;

async function json(response: Response): Promise<JsonResponse> {
  return response.json().catch(() => ({})) as Promise<JsonResponse>;
}

export async function getStorageProvider(): Promise<StorageProvider> {
  const response = await fetch('/api/storage/configuration', { cache: 'no-store' });
  const configuration = await json(response);
  if (!response.ok) {
    throw new MediaUploadError(configuration.error || 'Unable to load storage configuration', undefined, response.status);
  }
  return configuration.provider === 'stuffbox' ? 'stuffbox' : null;
}

export async function hasNewStuffboxConnection(startedAt: string): Promise<boolean> {
  const response = await fetch('/api/storage/configuration', { cache: 'no-store' });
  const configuration = await json(response);
  if (!response.ok) {
    throw new MediaUploadError(configuration.error || 'Unable to verify the Stuffbox connection', undefined, response.status);
  }

  const updatedAt = typeof configuration.stuffboxUpdatedAt === 'string'
    ? Date.parse(configuration.stuffboxUpdatedAt)
    : Number.NaN;
  return configuration.provider === 'stuffbox'
    && Number.isFinite(updatedAt)
    && updatedAt >= Date.parse(startedAt);
}

function directPut(
  uploadUrl: string,
  file: File,
  headers: Record<string, string>,
  onProgress?: (progress: number) => void,
): Promise<void> {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.open('PUT', uploadUrl);
    for (const [name, value] of Object.entries(headers)) request.setRequestHeader(name, value);
    request.upload.addEventListener('progress', (event) => {
      if (event.lengthComputable) onProgress?.(event.loaded / event.total);
    });
    request.addEventListener('load', () => {
      if (request.status >= 200 && request.status < 300) {
        onProgress?.(1);
        resolve();
      } else {
        reject(new MediaUploadError(`Stuffbox rejected the upload (${request.status})`, 'DIRECT_UPLOAD_FAILED', request.status));
      }
    });
    request.addEventListener('error', () => reject(new MediaUploadError(
      'The browser could not reach Stuffbox. Check its CORS and public URL settings.',
      'DIRECT_UPLOAD_FAILED',
    )));
    request.addEventListener('abort', () => reject(new MediaUploadError('Upload cancelled', 'UPLOAD_CANCELLED')));
    request.send(file);
  });
}

async function uploadToStuffbox(file: File, onProgress?: (progress: number) => void): Promise<UploadedMedia> {
  const sessionResponse = await fetch('/api/media/stuffbox/uploads', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ filename: file.name, mimeType: file.type, size: file.size }),
  });
  const session = await json(sessionResponse);
  if (!sessionResponse.ok) {
    throw new MediaUploadError(session.error || 'Unable to start upload', session.code, sessionResponse.status);
  }
  if (!session.id || !session.uploadUrl) {
    throw new MediaUploadError('Stuffbox returned an invalid upload session', 'INVALID_UPLOAD_SESSION');
  }

  await directPut(session.uploadUrl, file, session.requiredHeaders || {}, onProgress);

  const completeResponse = await fetch(`/api/media/stuffbox/uploads/${encodeURIComponent(session.id)}/complete`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: '{}',
  });
  const complete = await json(completeResponse);
  if (!completeResponse.ok || !complete.media?.url) {
    throw new MediaUploadError(complete.error || 'Unable to finish upload', complete.code, completeResponse.status);
  }
  return complete.media as UploadedMedia;
}

export async function uploadMediaFile(
  file: File,
  onProgress?: (progress: number) => void,
): Promise<UploadedMedia> {
  const provider = await getStorageProvider();
  if (provider === 'stuffbox') return uploadToStuffbox(file, onProgress);
  throw new MediaUploadError('Connect Stuffbox before uploading.', 'STORAGE_NOT_CONFIGURED', 409);
}
