// tests/api/04-aliases.ts
// 3-Tier Alias Resolution System Tests

import { join } from 'path';
import { api, log, runTest, uploadFile, waitForGeneration, testContext, resolveAlias, createTestImage, 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('ALIAS RESOLUTION TESTS');

  // Setup: Create a flow for testing
  const setupGen = await createTestImage('Setup image for alias tests', {
    flowAlias: '@alias-test-flow',
  });
  testContext.aliasFlowId = setupGen.flowId;
  log.info(`Test flow created: ${testContext.aliasFlowId}`);

  // Test 1: Technical alias @last
  await runTest('Technical alias - @last', async () => {
    const resolved = await resolveAlias('@last', testContext.aliasFlowId);

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

    if (!resolved.imageId) {
      throw new Error('No image resolved');
    }

    log.detail('Scope', resolved.scope);
    log.detail('Alias', '@last');
    log.detail('Image ID', resolved.imageId);
  });

  // Test 2: Technical alias @first
  await runTest('Technical alias - @first', async () => {
    const resolved = await resolveAlias('@first', testContext.aliasFlowId);

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

    log.detail('Scope', resolved.scope);
    log.detail('Alias', '@first');
  });

  // Test 3: Technical alias @upload
  await runTest('Technical alias - @upload', async () => {
    // First upload an image to the flow
    const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
    await uploadFile(fixturePath, {
      flowId: testContext.aliasFlowId,
      description: 'Uploaded for @upload test',
    });

    const resolved = await resolveAlias('@upload', testContext.aliasFlowId);

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

    log.detail('Scope', resolved.scope);
    log.detail('Alias', '@upload');
    log.detail('Image source', 'uploaded');
  });

  // Test 4: Technical alias requires flowId
  await runTest('Technical alias requires flow context', async () => {
    try {
      await resolveAlias('@last'); // No flowId
      throw new Error('Should require flowId');
    } catch (error: any) {
      if (error.message.includes('Should require')) {
        throw error;
      }
      log.detail('Correctly requires flowId', '✓');
    }
  });

  // Test 5: Flow-scoped alias
  await runTest('Flow-scoped alias resolution', async () => {
    const gen = await createTestImage('Image for flow alias', {
      flowId: testContext.aliasFlowId,
      flowAlias: '@flow-hero',
    });

    const resolved = await resolveAlias('@flow-hero', testContext.aliasFlowId);

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

    log.detail('Scope', resolved.scope);
    log.detail('Alias', '@flow-hero');
  });

  // Test 6: Project-scoped alias
  await runTest('Project-scoped alias resolution', async () => {
    const gen = await createTestImage('Image for project alias', {
      alias: '@project-logo',
      flowId: null, // Explicitly no flow
    });

    const resolved = await resolveAlias('@project-logo');

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

    log.detail('Scope', resolved.scope);
    log.detail('Alias', '@project-logo');
  });

  // Test 7: Alias priority - flow overrides project
  await runTest('Alias precedence - flow > project', async () => {
    // Create project alias
    const projectGen = await createTestImage('Project scoped image', {
      alias: '@priority-test',
      flowId: null,
    });

    // Create flow alias with same name
    const flowGen = await createTestImage('Flow scoped image', {
      flowId: testContext.aliasFlowId,
      flowAlias: '@priority-test',
    });

    // Without flow context - should get project
    const projectResolved = await resolveAlias('@priority-test');
    if (projectResolved.imageId !== projectGen.outputImageId) {
      throw new Error('Should resolve to project alias');
    }
    log.detail('Without flow context', 'resolved to project ✓');

    // With flow context - should get flow
    const flowResolved = await resolveAlias('@priority-test', testContext.aliasFlowId);
    if (flowResolved.imageId !== flowGen.outputImageId) {
      throw new Error('Should resolve to flow alias');
    }
    log.detail('With flow context', 'resolved to flow ✓');
  });

  // Test 8: Reserved alias validation
  await runTest('Reserved aliases cannot be assigned', async () => {
    const reservedAliases = ['@last', '@first', '@upload'];

    for (const reserved of reservedAliases) {
      try {
        const gen = await api(endpoints.generations, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            prompt: 'Test',
            aspectRatio: '1:1',
            alias: reserved,
          }),
        });

        // If we get here, it didn't throw - that's bad
        log.warning(`Reserved alias ${reserved} was allowed!`);
      } catch (error: any) {
        // Expected to fail
        log.detail(`${reserved} correctly blocked`, '✓');
      }
    }
  });

  // Test 9: Alias reassignment
  await runTest('Alias reassignment removes old', async () => {
    const gen1 = await createTestImage('First image', {
      alias: '@reassign-test',
      flowId: null,
    });

    const gen2 = await createTestImage('Second image', {
      alias: '@reassign-test',
      flowId: null,
    });

    // Check that gen2 has the alias
    const resolved = await resolveAlias('@reassign-test');
    if (resolved.imageId !== gen2.outputImageId) {
      throw new Error('Alias should be on second image');
    }

    // Check that gen1 lost the alias
    const img1 = await api(`${endpoints.images}/${gen1.outputImageId}`);
    if (img1.data.data.alias !== null) {
      throw new Error('First image should have lost alias');
    }

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

  // Test 10: Same alias in different flows
  await runTest('Same alias in different flows', async () => {
    // Create two flows with same alias
    const gen1 = await createTestImage('Flow 1 image', {
      flowAlias: '@shared-name',
    });

    const gen2 = await createTestImage('Flow 2 image', {
      flowAlias: '@shared-name',
    });

    // Resolve in each flow context
    const resolved1 = await resolveAlias('@shared-name', gen1.flowId);
    const resolved2 = await resolveAlias('@shared-name', gen2.flowId);

    if (resolved1.imageId === resolved2.imageId) {
      throw new Error('Should resolve to different images');
    }

    log.detail('Flow 1 image', resolved1.imageId.slice(0, 8));
    log.detail('Flow 2 image', resolved2.imageId.slice(0, 8));
    log.detail('Isolation confirmed', '✓');
  });

  // Test 11: Technical alias in generation prompt
  await runTest('Use technical alias in prompt', async () => {
    const result = await api(endpoints.generations, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        prompt: 'New variation based on @last',
        aspectRatio: '1:1',
        flowId: testContext.aliasFlowId,
      }),
    });

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

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

    // Check that @last was resolved
    const hasLast = generation.referencedImages?.some((ref: any) => ref.alias === '@last');
    if (!hasLast) {
      throw new Error('Technical alias not resolved in prompt');
    }

    log.detail('Technical alias resolved', '✓');
  });

  // Test 12: Upload with dual aliases
  await runTest('Upload with both project and flow alias', async () => {
    const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');

    const response = await uploadFile(fixturePath, {
      alias: '@dual-project',
      flowId: testContext.aliasFlowId,
      flowAlias: '@dual-flow',
    });

    // Verify both aliases work
    const projectResolved = await resolveAlias('@dual-project');
    const flowResolved = await resolveAlias('@dual-flow', testContext.aliasFlowId);

    if (projectResolved.imageId !== response.id || flowResolved.imageId !== response.id) {
      throw new Error('Both aliases should resolve to same image');
    }

    log.detail('Project alias', '@dual-project ✓');
    log.detail('Flow alias', '@dual-flow ✓');
  });

  log.section('ALIAS RESOLUTION TESTS COMPLETED');
}

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