// src/tests/integration/flyer-processing.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs/promises';
import path from 'path';
import * as apiClient from '../../services/apiClient';
import * as aiApiClient from '../../services/aiApiClient';
import * as db from '../../services/db/index.db';
import { getPool } from '../../services/db/connection.db';
import { generateFileChecksum } from '../../utils/checksum';
import type { User } from '../../types';

/**
 * @vitest-environment node
 */

const TEST_PASSWORD = 'a-much-stronger-password-for-testing-!@#$';

/**
 * Helper to create and log in a user for authenticated tests.
 */
const createAndLoginUser = async (email: string) => {
  const registerResponse = await apiClient.registerUser(email, TEST_PASSWORD, 'Flyer Uploader');
  if (!registerResponse.ok) {
      const errorData = await registerResponse.json();
      throw new Error(errorData.message || 'Test registration failed');
  }
  const loginResponse = await apiClient.loginUser(email, TEST_PASSWORD, false);
  const { user, token } = await loginResponse.json();
  return { user, token };
};

describe('Flyer Processing Background Job Integration Test', () => {
  const createdUserIds: string[] = [];
  const createdFlyerIds: number[] = [];

  beforeAll(async () => {
    // This setup is now simpler as the worker handles fetching master items.
  });

  afterAll(async () => {
    // Clean up all entities created during the tests using their collected IDs.
    // This is safer than using LIKE queries.
    if (createdFlyerIds.length > 0) {
      await getPool().query('DELETE FROM public.flyers WHERE flyer_id = ANY($1::bigint[])', [createdFlyerIds]);
    }
    if (createdUserIds.length > 0) {
      await getPool().query('DELETE FROM public.users WHERE user_id = ANY($1::uuid[])', [createdUserIds]);
    }

    // Clean up any files created in the flyer-images directory during tests.
    const uploadDir = path.resolve(__dirname, '../../../flyer-images');
    try {
      const files = await fs.readdir(uploadDir);
      // Use a more specific filter to only target files created by this test suite.
      const testFiles = files.filter(f => f.includes('test-flyer-image'));
      for (const file of testFiles) {
        await fs.unlink(path.join(uploadDir, file));
        // Also try to remove from the icons subdirectory
        await fs.unlink(path.join(uploadDir, 'icons', `icon-${file}`)).catch(() => {});
      }
    } catch (error) {
      console.error('Error during test file cleanup:', error);
    }
  });

  /**
   * This is the new end-to-end test for the background job processing flow.
   * It uploads a file, polls for completion, and verifies the result in the database.
   */
  const runBackgroundProcessingTest = async (user?: User, token?: string) => {
    // Arrange: Load a mock flyer PDF.
    const imagePath = path.resolve(__dirname, '../assets/test-flyer-image.jpg');
    const imageBuffer = await fs.readFile(imagePath);
    const mockImageFile = new File([imageBuffer], 'test-flyer-image.jpg', { type: 'image/jpeg' });
    const checksum = await generateFileChecksum(mockImageFile);

    // Act 1: Upload the file to start the background job.
    const uploadResponse = await aiApiClient.uploadAndProcessFlyer(mockImageFile, checksum, token);
    const { jobId } = await uploadResponse.json();

    // Assert 1: Check that a job ID was returned.
    expect(jobId).toBeTypeOf('string');

    // Act 2: Poll for the job status until it completes.
    let jobStatus;
    const maxRetries = 20; // Poll for up to 60 seconds (20 * 3s)
    for (let i = 0; i < maxRetries; i++) {
      await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds between polls
      const statusResponse = await aiApiClient.getJobStatus(jobId, token);
      jobStatus = await statusResponse.json();
      if (jobStatus.state === 'completed' || jobStatus.state === 'failed') {
        break;
      }
    }

    // Assert 2: Check that the job completed successfully.
    expect(jobStatus?.state).toBe('completed');
    const flyerId = jobStatus?.returnValue?.flyerId;
    expect(flyerId).toBeTypeOf('number');
    createdFlyerIds.push(flyerId); // Track for cleanup

    // Assert 3: Verify the flyer and its items were actually saved in the database.
    const savedFlyer = await db.flyerRepo.findFlyerByChecksum(checksum);
    expect(savedFlyer).toBeDefined();
    expect(savedFlyer?.flyer_id).toBe(flyerId);

    const items = await db.flyerRepo.getFlyerItems(flyerId);
    // The stubbed AI response returns items, so we expect them to be here.
    expect(items.length).toBeGreaterThan(0);
    expect(items[0].item).toBeTypeOf('string');

    // Assert 4: Verify user association is correct.
    if (token) {
      expect(savedFlyer?.uploaded_by).toBe(user?.user_id);
    } else {
      expect(savedFlyer?.uploaded_by).toBe(null);
    }
  };

  it('should successfully process a flyer for an AUTHENTICATED user via the background queue', async ({ onTestFinished }) => {
    // Arrange: Create a new user specifically for this test.
    const email = `auth-flyer-user-${Date.now()}@example.com`;
    const { user, token } = await createAndLoginUser(email);
    createdUserIds.push(user.user_id); // Track for cleanup

    // Use a cleanup function to delete the user even if the test fails.
    onTestFinished(async () => {
      await getPool().query('DELETE FROM public.users WHERE user_id = $1', [user.user_id]);
    });

    // Act & Assert
    await runBackgroundProcessingTest(user, token);
  }, 120000); // Increase timeout to 120 seconds for this long-running test

  it('should successfully process a flyer for an ANONYMOUS user via the background queue', async () => {
    // Act & Assert: Call the test helper without a user or token.
    await runBackgroundProcessingTest();
  }, 120000); // Increase timeout to 120 seconds for this long-running test
});
