// tests/api/02-basic.ts
// Image Upload and CRUD Operations

import { join } from 'path';
import { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext, verifyImageAccessible, resolveAlias, exitWithTestResults } from './utils';
import { config, endpoints } from './config';

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

async function main() {
  log.section('IMAGE UPLOAD & CRUD TESTS');

  // Test 1: Upload image with project-scoped alias
  await runTest('Upload image with project alias', async () => {
    const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');

    const response = await uploadFile(fixturePath, {
      alias: '@test-logo',
      description: 'Test logo image for CRUD tests',
    });

    if (!response || !response.id) {
      throw new Error('No image returned');
    }

    if (response.alias !== '@test-logo') {
      throw new Error('Alias not set correctly');
    }

    if (response.source !== 'uploaded') {
      throw new Error('Source should be "uploaded"');
    }

    testContext.uploadedImageId = response.id;
    log.detail('Image ID', response.id);
    log.detail('Storage Key', response.storageKey);
    log.detail('Alias', response.alias);
    log.detail('Source', response.source);
  });

  // Test 1.5: Validate storage path format
  await runTest('Storage path format validation', async () => {
    // Get uploaded image
    const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
    const image = result.data.data;

    // Validate storageKey format: {orgSlug}/{projectSlug}/img/{uuid}
    const keyRegex = /^[\w-]+\/[\w-]+\/img\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
    if (!keyRegex.test(image.storageKey)) {
      throw new Error(`Invalid storageKey format: ${image.storageKey}. Expected: {org}/{proj}/img/{uuid}`);
    }

    // Validate storageUrl format: https://.../img/{uuid}
    const urlRegex = /^https?:\/\/[^\/]+\/[\w-]+\/[\w-]+\/img\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
    if (!urlRegex.test(image.storageUrl)) {
      throw new Error(`Invalid storageUrl format: ${image.storageUrl}. Expected: https://.../img/{uuid}`);
    }

    // Verify imageId matches the UUID in path
    const pathParts = image.storageKey.split('/');
    const uuidFromPath = pathParts[3];
    if (uuidFromPath !== image.id) {
      throw new Error(`storageKey UUID (${uuidFromPath}) doesn't match image.id (${image.id})`);
    }

    log.detail('storageKey', image.storageKey);
    log.detail('storageUrl', image.storageUrl);
    log.detail('UUID in path = image.id', '✓');
    log.detail('Format', '{org}/{proj}/img/{uuid}');
  });

  // Test 2: Upload image without alias
  await runTest('Upload image without alias', async () => {
    const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');

    const response = await uploadFile(fixturePath, {
      description: 'Image without alias',
    });

    if (!response || !response.id) {
      throw new Error('No image returned');
    }

    if (response.alias !== null) {
      throw new Error('Alias should be null');
    }

    log.detail('Image ID', response.id);
    log.detail('Alias', 'null (as expected)');
  });

  // Test 3: List all images
  await runTest('List all images', async () => {
    const result = await api(endpoints.images);

    if (!result.data.data || !Array.isArray(result.data.data)) {
      throw new Error('No images array returned');
    }

    log.detail('Total images', result.data.data.length);

    // Find our uploaded image
    const found = result.data.data.find((img: any) => img.id === testContext.uploadedImageId);
    if (!found) {
      throw new Error('Uploaded image not in list');
    }

    log.detail('Found our image', '✓');
  });

  // Test 4: List images with source filter
  await runTest('List images - filter by source=uploaded', async () => {
    const result = await api(`${endpoints.images}?source=uploaded`);

    if (!result.data.data || !Array.isArray(result.data.data)) {
      throw new Error('No images array returned');
    }

    // All should be uploaded
    const allUploaded = result.data.data.every((img: any) => img.source === 'uploaded');
    if (!allUploaded) {
      throw new Error('Source filter not working');
    }

    log.detail('Uploaded images', result.data.data.length);
  });

  // Test 5: List images with pagination
  await runTest('List images with pagination', async () => {
    const result = await api(`${endpoints.images}?limit=3&offset=0`);

    if (!result.data.pagination) {
      throw new Error('No pagination data');
    }

    log.detail('Limit', result.data.pagination.limit);
    log.detail('Offset', result.data.pagination.offset);
    log.detail('Total', result.data.pagination.total);
    log.detail('Has more', result.data.pagination.hasMore);
  });

  // Test 6: Get image by ID
  await runTest('Get image by ID', async () => {
    const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`);

    if (!result.data.data) {
      throw new Error('Image not found');
    }

    const image = result.data.data;

    // Verify fields
    if (!image.id) throw new Error('Missing id');
    if (!image.storageKey) throw new Error('Missing storageKey');
    if (!image.storageUrl) throw new Error('Missing storageUrl');
    if (!image.source) throw new Error('Missing source');

    log.detail('Image ID', image.id);
    log.detail('Source', image.source);
    log.detail('File size', `${image.fileSize || 0} bytes`);
    log.detail('Alias', image.alias || 'null');
  });

  // Test 7: Get image by alias (using resolve endpoint)
  await runTest('Resolve project-scoped alias', async () => {
    const resolved = await resolveAlias('@test-logo');

    if (!resolved.imageId) {
      throw new Error('Alias not resolved');
    }

    if (resolved.imageId !== testContext.uploadedImageId) {
      throw new Error('Resolved to wrong image');
    }

    if (resolved.scope !== 'project') {
      throw new Error(`Wrong scope: ${resolved.scope}`);
    }

    log.detail('Resolved image ID', resolved.imageId);
    log.detail('Scope', resolved.scope);
    log.detail('Alias', resolved.alias);
  });

  // Test 8: Update image metadata
  await runTest('Update image metadata', async () => {
    const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        focalPoint: { x: 0.5, y: 0.3 },
        meta: {
          description: 'Updated description',
          tags: ['test', 'logo', 'updated'],
        },
      }),
    });

    if (!result.data.data) {
      throw new Error('No image returned');
    }

    // Verify update by fetching again
    const updated = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
    const image = updated.data.data;

    if (!image.focalPoint || image.focalPoint.x !== 0.5 || image.focalPoint.y !== 0.3) {
      throw new Error('Focal point not updated');
    }

    log.detail('Focal point', JSON.stringify(image.focalPoint));
    log.detail('Meta', JSON.stringify(image.meta));
  });

  // Test 9: Update image alias (dedicated endpoint)
  await runTest('Update image alias', async () => {
    const result = await api(`${endpoints.images}/${testContext.uploadedImageId}/alias`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        alias: '@new-test-logo',
      }),
    });

    if (!result.data.data) {
      throw new Error('No image returned');
    }

    // Verify new alias works
    const resolved = await resolveAlias('@new-test-logo');
    if (resolved.imageId !== testContext.uploadedImageId) {
      throw new Error('New alias not working');
    }

    log.detail('New alias', '@new-test-logo');
    log.detail('Resolved', '✓');
  });

  // Test 10: Verify old alias doesn't work after update
  await runTest('Old alias should not resolve after update', async () => {
    try {
      await resolveAlias('@test-logo');
      throw new Error('Old alias should not resolve');
    } catch (error: any) {
      // Expected to fail
      if (error.message.includes('should not resolve')) {
        throw error;
      }
      log.detail('Old alias correctly invalid', '✓');
    }
  });

  // Test 11: Remove image alias
  await runTest('Remove image alias', async () => {
    await api(`${endpoints.images}/${testContext.uploadedImageId}/alias`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        alias: null,
      }),
    });

    // Verify image exists but has no alias
    const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
    if (result.data.data.alias !== null) {
      throw new Error('Alias should be null');
    }

    // Verify alias resolution fails
    try {
      await resolveAlias('@new-test-logo');
      throw new Error('Removed alias should not resolve');
    } catch (error: any) {
      if (error.message.includes('should not resolve')) {
        throw error;
      }
      log.detail('Alias removed', '✓');
    }
  });

  // Test 12: Generate image with manual reference
  await runTest('Generate with manual reference image', async () => {
    // First, reassign alias for reference
    await api(`${endpoints.images}/${testContext.uploadedImageId}/alias`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        alias: '@reference-logo',
      }),
    });

    const result = await api(endpoints.generations, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        prompt: 'A product photo with the logo in corner',
        aspectRatio: '1:1',
        referenceImages: ['@reference-logo'],
      }),
    });

    const generation = await waitForGeneration(result.data.data.id);

    if (generation.status !== 'success') {
      throw new Error(`Generation failed: ${generation.errorMessage}`);
    }

    // Verify referenced images tracked
    if (!generation.referencedImages || generation.referencedImages.length === 0) {
      throw new Error('Referenced images not tracked');
    }

    const refFound = generation.referencedImages.some(
      (ref: any) => ref.alias === '@reference-logo'
    );

    if (!refFound) {
      throw new Error('Reference image not found in referencedImages');
    }

    log.detail('Generation ID', generation.id);
    log.detail('Referenced images', generation.referencedImages.length);

    // Save generated image
    if (generation.outputImageId) {
      const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
      const imageUrl = imageResult.data.data.storageUrl;
      const imageResponse = await fetch(imageUrl);
      const imageBuffer = await imageResponse.arrayBuffer();
      await saveImage(imageBuffer, 'gen-with-reference.png');
    }
  });

  // Test 13: Generate with auto-detected reference in prompt
  await runTest('Generate with auto-detected reference', async () => {
    const result = await api(endpoints.generations, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        prompt: 'Create banner using @reference-logo with blue background',
        aspectRatio: '16:9',
        // NOTE: referenceImages NOT provided, should auto-detect
      }),
    });

    const generation = await waitForGeneration(result.data.data.id);

    if (generation.status !== 'success') {
      throw new Error(`Generation failed: ${generation.errorMessage}`);
    }

    // Verify auto-detection worked
    if (!generation.referencedImages || generation.referencedImages.length === 0) {
      throw new Error('Auto-detection did not work');
    }

    const autoDetected = generation.referencedImages.some(
      (ref: any) => ref.alias === '@reference-logo'
    );

    if (!autoDetected) {
      throw new Error('Reference not auto-detected from prompt');
    }

    log.detail('Auto-detected references', generation.referencedImages.length);
  });

  // Test 14: Generate with project alias assignment
  await runTest('Generate with project alias assignment', async () => {
    const result = await api(endpoints.generations, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        prompt: 'A hero banner image',
        aspectRatio: '21:9',
        alias: '@hero-banner',
      }),
    });

    const generation = await waitForGeneration(result.data.data.id);

    if (generation.status !== 'success') {
      throw new Error(`Generation failed: ${generation.errorMessage}`);
    }

    // Verify alias assigned to output image
    const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
    const image = imageResult.data.data;

    if (image.alias !== '@hero-banner') {
      throw new Error('Alias not assigned to output image');
    }

    // Verify alias resolution works
    const resolved = await resolveAlias('@hero-banner');
    if (resolved.imageId !== generation.outputImageId) {
      throw new Error('Alias resolution failed');
    }

    log.detail('Output image alias', image.alias);
    log.detail('Alias resolution', '✓');

    testContext.heroBannerId = generation.outputImageId;
  });

  // Test 15: Alias conflict - new generation overwrites
  await runTest('Alias conflict resolution', async () => {
    // First generation has @hero alias (from previous test)
    const firstImageId = testContext.heroBannerId;

    // Create second generation with same alias
    const result = await api(endpoints.generations, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        prompt: 'A different hero image',
        aspectRatio: '21:9',
        alias: '@hero-banner',
      }),
    });

    const generation = await waitForGeneration(result.data.data.id);

    if (generation.status !== 'success') {
      throw new Error(`Generation failed: ${generation.errorMessage}`);
    }

    const secondImageId = generation.outputImageId;

    // Verify second image has the alias
    const resolved = await resolveAlias('@hero-banner');
    if (resolved.imageId !== secondImageId) {
      throw new Error('Second image should have the alias');
    }

    // Verify first image lost the alias but still exists
    const firstImage = await api(`${endpoints.images}/${firstImageId}`);
    if (firstImage.data.data.alias !== null) {
      throw new Error('First image should have lost the alias');
    }

    log.detail('Second image has alias', '✓');
    log.detail('First image preserved', '✓');
    log.detail('First image alias removed', '✓');
  });

  log.section('IMAGE UPLOAD & CRUD TESTS COMPLETED');
}

main()
  .then(() => exitWithTestResults())
  .catch((error) => {
    console.error('Unexpected error:', error);
    process.exit(1);
  });
