// src/tests/integration/gamification.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import path from 'path';
import fs from 'node:fs/promises';
import { createAndLoginUser } from '../utils/testHelpers';
import { generateFileChecksum } from '../../utils/checksum';
import * as db from '../../services/db/index.db';
import { cleanupDb } from '../utils/cleanup';
import { logger } from '../../services/logger.server';
import type { UserProfile, UserAchievement, LeaderboardUser, Achievement  } from '../../types';
import { cleanupFiles } from '../utils/cleanupFiles';

/**
 * @vitest-environment node
 */

const request = supertest(app);

describe('Gamification Flow Integration Test', () => {
  let testUser: UserProfile;
  let authToken: string;
  const createdFlyerIds: number[] = [];
  const createdFilePaths: string[] = [];

  beforeAll(async () => {
    // Create a new user specifically for this test suite to ensure a clean slate.
    ({ user: testUser, token: authToken } = await createAndLoginUser({
      email: `gamification-user-${Date.now()}@example.com`,
      fullName: 'Gamification Tester',
      request,
    }));
  });

  afterAll(async () => {
    await cleanupDb({
      userIds: testUser ? [testUser.user.user_id] : [],
      flyerIds: createdFlyerIds,
    });
    await cleanupFiles(createdFilePaths);
  });

  it(
    'should award the "First Upload" achievement after a user successfully uploads and processes their first flyer',
    async () => {
      // --- Arrange: Prepare a unique flyer file for upload ---
      const imagePath = path.resolve(__dirname, '../assets/test-flyer-image.jpg');
      const imageBuffer = await fs.readFile(imagePath);
      const uniqueContent = Buffer.concat([imageBuffer, Buffer.from(Date.now().toString())]);
      const uniqueFileName = `gamification-test-flyer-${Date.now()}.jpg`;
      const mockImageFile = new File([uniqueContent], uniqueFileName, { type: 'image/jpeg' });
      const checksum = await generateFileChecksum(mockImageFile);

      // Track created files for cleanup
      const uploadDir = path.resolve(__dirname, '../../../flyer-images');
      createdFilePaths.push(path.join(uploadDir, uniqueFileName));
      const iconFileName = `icon-${path.parse(uniqueFileName).name}.webp`;
      createdFilePaths.push(path.join(uploadDir, 'icons', iconFileName));

      // --- Act 1: Upload the flyer to trigger the background job ---
      const uploadResponse = await request
        .post('/api/ai/upload-and-process')
        .set('Authorization', `Bearer ${authToken}`)
        .field('checksum', checksum)
        .attach('flyerFile', uniqueContent, uniqueFileName);

      const { jobId } = uploadResponse.body;
      expect(jobId).toBeTypeOf('string');

      // --- Act 2: Poll for job completion ---
      let jobStatus;
      const maxRetries = 30; // Poll for up to 90 seconds
      for (let i = 0; i < maxRetries; i++) {
        await new Promise((resolve) => setTimeout(resolve, 3000));
        const statusResponse = await request
          .get(`/api/ai/jobs/${jobId}/status`)
          .set('Authorization', `Bearer ${authToken}`);
        jobStatus = statusResponse.body;
        if (jobStatus.state === 'completed' || jobStatus.state === 'failed') {
          break;
        }
      }

      // --- Assert 1: Verify the job completed successfully ---
      if (jobStatus?.state === 'failed') {
        console.error('[DEBUG] Gamification test job failed:', jobStatus.failedReason);
      }
      expect(jobStatus?.state).toBe('completed');
      const flyerId = jobStatus?.returnValue?.flyerId;
      expect(flyerId).toBeTypeOf('number');
      createdFlyerIds.push(flyerId); // Track for cleanup

      // --- Assert 1.5: Verify the flyer was saved with the correct original filename ---
      const savedFlyer = await db.flyerRepo.findFlyerByChecksum(checksum, logger);
      expect(savedFlyer).toBeDefined();
      expect(savedFlyer?.file_name).toBe(uniqueFileName);
      // Also add the final processed image path to the cleanup list.
      // This is important because JPEGs are re-processed to strip EXIF data, creating a new file.
      const savedImagePath = path.join(uploadDir, path.basename(savedFlyer!.image_url));
      createdFilePaths.push(savedImagePath);

      // --- Act 3: Fetch the user's achievements ---
      const achievementsResponse = await request
        .get('/api/achievements/me')
        .set('Authorization', `Bearer ${authToken}`);
      const userAchievements: (UserAchievement & Achievement)[] = achievementsResponse.body;

      // --- Assert 2: Verify the "First-Upload" achievement was awarded ---
      // The 'user_registered' achievement is awarded on creation, so we expect at least two.
      expect(userAchievements.length).toBeGreaterThanOrEqual(2);
      const firstUploadAchievement = userAchievements.find((ach) => ach.name === 'First-Upload');
      expect(firstUploadAchievement).toBeDefined();
      expect(firstUploadAchievement?.points_value).toBeGreaterThan(0);

      // --- Act 4: Fetch the leaderboard ---
      const leaderboardResponse = await request.get('/api/achievements/leaderboard');
      const leaderboard: LeaderboardUser[] = leaderboardResponse.body;

      // --- Assert 3: Verify the user is on the leaderboard with points ---
      const userOnLeaderboard = leaderboard.find((u) => u.user_id === testUser.user.user_id);
      expect(userOnLeaderboard).toBeDefined();
      // The user should have points from 'user_registered' and 'First-Upload'.
      // We check that the points are greater than or equal to the points from the upload achievement.
      expect(Number(userOnLeaderboard?.points)).toBeGreaterThanOrEqual(
        firstUploadAchievement!.points_value,
      );
    },
    120000, // Increase timeout to 120 seconds for this long-running test
  );
});