import api from "./api";
import {
  resizeImage,
  getVariantKey,
  getSizeSuffix,
} from "../utils/imageResizer";

/**
 * Get the public URL for an image (S3 only)
 */
export const getPublicImageUrl = (
  imagePath: string | null | undefined
): string => {
  if (!imagePath) return "";

  // Already a full S3 URL
  if (imagePath.startsWith("https://") && imagePath.includes("s3.")) {
    return imagePath;
  }

  // S3 key (e.g., "profiles/uuid.jpg", "items/uuid.jpg", "forum/uuid.jpg")
  const s3Bucket = process.env.REACT_APP_S3_BUCKET || "";
  const s3Region = process.env.REACT_APP_AWS_REGION || "us-east-1";
  return `https://${s3Bucket}.s3.${s3Region}.amazonaws.com/${imagePath}`;
};

export interface PresignedUrlResponse {
  uploadUrl: string;
  key: string; // Final key for database storage
  stagingKey: string | null; // Actual upload location (when image processing enabled)
  publicUrl: string;
  expiresAt: string;
}

export type UploadType =
  | "profile"
  | "item"
  | "message"
  | "forum"
  | "condition-check";

interface UploadOptions {
  onProgress?: (percent: number) => void;
  maxRetries?: number;
}

/**
 * Get a presigned URL for uploading a single file
 */
export async function getPresignedUrl(
  uploadType: UploadType,
  file: File
): Promise<PresignedUrlResponse> {
  const response = await api.post("/upload/presign", {
    uploadType,
    contentType: file.type,
    fileName: file.name,
    fileSize: file.size,
  });
  return response.data;
}

interface BatchPresignResponse {
  uploads: PresignedUrlResponse[];
  baseKey: string;
}

/**
 * Get presigned URLs for uploading multiple files
 * All files share the same base UUID for coordinated variant uploads
 */
export async function getPresignedUrls(
  uploadType: UploadType,
  files: File[]
): Promise<BatchPresignResponse> {
  const response = await api.post("/upload/presign-batch", {
    uploadType,
    files: files.map((f) => ({
      contentType: f.type,
      fileName: f.name,
      fileSize: f.size,
    })),
  });
  return { uploads: response.data.uploads, baseKey: response.data.baseKey };
}

/**
 * Upload a file directly to S3 using a presigned URL
 */
export async function uploadToS3(
  file: File,
  uploadUrl: string,
  options: UploadOptions = {}
): Promise<void> {
  const { onProgress, maxRetries = 3 } = options;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      await new Promise<void>((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("PUT", uploadUrl, true);
        xhr.setRequestHeader("Content-Type", file.type);

        if (onProgress) {
          xhr.upload.onprogress = (e) => {
            if (e.lengthComputable) {
              onProgress(Math.round((e.loaded / e.total) * 100));
            }
          };
        }

        xhr.onload = () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve();
          } else {
            reject(new Error(`HTTP ${xhr.status}`));
          }
        };

        xhr.onerror = () => reject(new Error("Network error"));
        xhr.send(file);
      });
      return;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      // Exponential backoff
      await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}

/**
 * Confirm that files have been uploaded to S3
 */
export async function confirmUploads(
  keys: string[]
): Promise<{ confirmed: string[]; total: number }> {
  const response = await api.post("/upload/confirm", { keys });
  return response.data;
}

/**
 * Upload a single file to S3 (complete flow)
 */
export async function uploadFile(
  uploadType: UploadType,
  file: File,
  options: UploadOptions = {}
): Promise<{ key: string; publicUrl: string }> {
  // Get presigned URL
  const presigned = await getPresignedUrl(uploadType, file);

  // Upload to S3
  await uploadToS3(file, presigned.uploadUrl, options);

  // Confirm upload - use stagingKey if present (image processing enabled), else key
  const confirmKey = presigned.stagingKey || presigned.key;
  const { confirmed } = await confirmUploads([confirmKey]);

  if (confirmed.length === 0) {
    throw new Error("Upload verification failed");
  }

  // Return final key for database storage
  return { key: presigned.key, publicUrl: presigned.publicUrl };
}

/**
 * Get a signed URL for accessing private content (messages, condition-checks)
 */
export async function getSignedUrl(key: string): Promise<string> {
  const response = await api.get(
    `/upload/signed-url/${encodeURIComponent(key)}`
  );
  return response.data.url;
}

/**
 * Get a signed URL for a specific image size variant (private content)
 * Backend will validate ownership using the base key
 */
export async function getSignedImageUrl(
  baseKey: string,
  size: "thumbnail" | "medium" | "original" = "original"
): Promise<string> {
  const suffix = getSizeSuffix(size);
  const variantKey = getVariantKey(baseKey, suffix);
  return getSignedUrl(variantKey);
}

/**
 * Get URL for a specific image size variant
 * Falls back to original if variant doesn't exist (backward compatibility)
 */
export function getImageUrl(
  baseKey: string | null | undefined,
  size: "thumbnail" | "medium" | "original" = "original"
): string {
  if (!baseKey) return "";

  const suffix = getSizeSuffix(size);
  const variantKey = getVariantKey(baseKey, suffix);

  return getPublicImageUrl(variantKey);
}

export interface UploadWithResizeOptions extends UploadOptions {
  skipResize?: boolean;
}

/**
 * Upload a single image with all size variants (thumbnail, medium, original)
 * Returns the base key (original, without suffix) for database storage
 */
export async function uploadImageWithVariants(
  uploadType: UploadType,
  file: File,
  options: UploadWithResizeOptions = {}
): Promise<{ baseKey: string; publicUrl: string; variants: string[] }> {
  const { onProgress, skipResize } = options;

  // If skipping resize, use regular upload
  if (skipResize) {
    const result = await uploadFile(uploadType, file, { onProgress });
    return { baseKey: result.key, publicUrl: result.publicUrl, variants: [result.key] };
  }

  // Generate resized variants
  const resizedImages = await resizeImage(file);

  if (resizedImages.length === 0) {
    throw new Error("Failed to resize image");
  }

  // Get presigned URLs for all variants (all share same base UUID)
  const files = resizedImages.map((r) => r.file);
  const { uploads: presignedUrls } = await getPresignedUrls(uploadType, files);

  // Upload all variants in parallel with combined progress
  const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
  let uploadedBytes = 0;

  await Promise.all(
    files.map((variantFile, i) =>
      uploadToS3(variantFile, presignedUrls[i].uploadUrl, {
        onProgress: (percent) => {
          if (onProgress) {
            const fileContribution = (variantFile.size / totalBytes) * percent;
            // Approximate combined progress
            onProgress(Math.min(99, Math.round(uploadedBytes / totalBytes * 100 + fileContribution)));
          }
        },
      }).then(() => {
        uploadedBytes += files[i].size;
      })
    )
  );

  // Confirm all uploads - use stagingKey if present (image processing enabled), else key
  const confirmKeys = presignedUrls.map((p) => p.stagingKey || p.key);
  await confirmUploads(confirmKeys);

  if (onProgress) onProgress(100);

  // Use the final keys for database storage (not staging keys)
  const finalKeys = presignedUrls.map((p) => p.key);
  const originalKey = finalKeys.find((k) => !k.includes("_th") && !k.includes("_md")) || finalKeys[0];

  return {
    baseKey: originalKey,
    publicUrl: getPublicImageUrl(originalKey),
    variants: finalKeys,
  };
}

/**
 * Upload multiple images with all size variants
 * Returns array of base keys for database storage
 */
export async function uploadImagesWithVariants(
  uploadType: UploadType,
  files: File[],
  options: UploadWithResizeOptions = {}
): Promise<{ baseKey: string; publicUrl: string }[]> {
  if (files.length === 0) return [];

  const results = await Promise.all(
    files.map((file) => uploadImageWithVariants(uploadType, file, options))
  );

  return results.map((r) => ({ baseKey: r.baseKey, publicUrl: r.publicUrl }));
}
