// src/tests/integration/admin.integration.test.ts
import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { getPool } from '../../services/db/connection.db';
import type { UserProfile } from '../../types';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';

/**
 * @vitest-environment node
 */
const request = supertest(app);

describe('Admin API Routes Integration Tests', () => {
  let adminToken: string;
  let adminUser: UserProfile;
  let regularUser: UserProfile;
  let regularUserToken: string;
  const createdUserIds: string[] = [];
  const createdStoreIds: number[] = [];

  beforeAll(async () => {
    // Create a fresh admin user and a regular user for this test suite
    // Using unique emails to prevent test pollution from other integration test files.
    ({ user: adminUser, token: adminToken } = await createAndLoginUser({
      email: `admin-integration-${Date.now()}@test.com`,
      role: 'admin',
      fullName: 'Admin Test User',
      request, // Pass supertest request to ensure user is created in the test DB
    }));
    createdUserIds.push(adminUser.user.user_id);

    ({ user: regularUser, token: regularUserToken } = await createAndLoginUser({
      email: `regular-integration-${Date.now()}@test.com`,
      fullName: 'Regular User',
      request, // Pass supertest request
    }));
    createdUserIds.push(regularUser.user.user_id);
  });

  afterAll(async () => {
    await cleanupDb({
      userIds: createdUserIds,
      storeIds: createdStoreIds,
    });
  });

  describe('GET /api/admin/stats', () => {
    it('should allow an admin to fetch application stats', async () => {
      const response = await request
        .get('/api/admin/stats')
        .set('Authorization', `Bearer ${adminToken}`);
      const stats = response.body;
      // DEBUG: Log response if it fails expectation
      if (response.status !== 200) {
        console.error('[DEBUG] GET /api/admin/stats failed:', response.status, response.body);
      }
      expect(stats).toBeDefined();
      expect(stats).toHaveProperty('flyerCount');
      expect(stats).toHaveProperty('userCount');
      expect(stats).toHaveProperty('flyerItemCount');
    });

    it('should forbid a regular user from fetching application stats', async () => {
      const response = await request
        .get('/api/admin/stats')
        .set('Authorization', `Bearer ${regularUserToken}`);
      expect(response.status).toBe(403);
      const errorData = response.body;
      expect(errorData.message).toBe('Forbidden: Administrator access required.');
    });
  });

  describe('GET /api/admin/stats/daily', () => {
    it('should allow an admin to fetch daily stats', async () => {
      const response = await request
        .get('/api/admin/stats/daily')
        .set('Authorization', `Bearer ${adminToken}`);
      const dailyStats = response.body;
      expect(dailyStats).toBeDefined();
      expect(Array.isArray(dailyStats)).toBe(true);
      // We just created users in beforeAll, so we should have data
      expect(dailyStats.length).toBe(30);
      expect(dailyStats[0]).toHaveProperty('date');
      expect(dailyStats[0]).toHaveProperty('new_users');
      expect(dailyStats[0]).toHaveProperty('new_flyers');
    });

    it('should forbid a regular user from fetching daily stats', async () => {
      const response = await request
        .get('/api/admin/stats/daily')
        .set('Authorization', `Bearer ${regularUserToken}`);
      expect(response.status).toBe(403);
      const errorData = response.body;
      expect(errorData.message).toBe('Forbidden: Administrator access required.');
    });
  });

  describe('GET /api/admin/corrections', () => {
    it('should allow an admin to fetch suggested corrections', async () => {
      // This test just verifies access and correct response shape.
      // More detailed tests would require seeding corrections.
      const response = await request
        .get('/api/admin/corrections')
        .set('Authorization', `Bearer ${adminToken}`);
      const corrections = response.body;
      expect(corrections).toBeDefined();
      expect(Array.isArray(corrections)).toBe(true);
    });

    it('should forbid a regular user from fetching suggested corrections', async () => {
      const response = await request
        .get('/api/admin/corrections')
        .set('Authorization', `Bearer ${regularUserToken}`);
      expect(response.status).toBe(403);
      const errorData = response.body;
      expect(errorData.message).toBe('Forbidden: Administrator access required.');
    });
  });

  describe('GET /api/admin/brands', () => {
    it('should allow an admin to fetch all brands', async () => {
      const response = await request
        .get('/api/admin/brands')
        .set('Authorization', `Bearer ${adminToken}`);
      const brands = response.body;
      expect(brands).toBeDefined();
      expect(Array.isArray(brands)).toBe(true);
      // Even if no brands exist, it should return an array.
      // (We rely on seed or empty state here, which is fine for a read test,
      // but creating a brand would be strictly better if we wanted to assert length > 0)
    });

    it('should forbid a regular user from fetching all brands', async () => {
      const response = await request
        .get('/api/admin/brands')
        .set('Authorization', `Bearer ${regularUserToken}`);
      expect(response.status).toBe(403);
      const errorData = response.body;
      expect(errorData.message).toBe('Forbidden: Administrator access required.');
    });
  });

  describe('Admin Data Modification Routes', () => {
    let testStoreId: number;
    let testFlyerItemId: number;
    let testCorrectionId: number;

    // Create a store and flyer once for all tests in this block.
    beforeAll(async () => {
      // Create a dummy store and flyer to ensure foreign keys exist
      // Use a unique name to prevent conflicts if tests are run in parallel or without full DB reset.
      const storeName = `Admin Test Store - ${Date.now()}`;
      const storeRes = await getPool().query(
        `INSERT INTO public.stores (name) VALUES ($1) RETURNING store_id`,
        [storeName],
      );
      testStoreId = storeRes.rows[0].store_id;
      createdStoreIds.push(testStoreId);
    });

    // Before each modification test, create a fresh flyer item and a correction for it.
    beforeEach(async () => {
      const flyerRes = await getPool().query(
        `INSERT INTO public.flyers (store_id, file_name, image_url, item_count, checksum) 
         VALUES ($1, 'admin-test.jpg', 'http://test.com/img.jpg', 1, $2) RETURNING flyer_id`,
        [testStoreId, `checksum-${Date.now()}-${Math.random()}`],
      );
      const flyerId = flyerRes.rows[0].flyer_id;

      const flyerItemRes = await getPool().query(
        `INSERT INTO public.flyer_items (flyer_id, item, price_display, price_in_cents, quantity)
         VALUES ($1, 'Test Item for Correction', '$1.99', 199, 'each') RETURNING flyer_item_id`,
        [flyerId],
      );
      testFlyerItemId = flyerItemRes.rows[0].flyer_item_id;

      const correctionRes = await getPool().query(
        `INSERT INTO public.suggested_corrections (flyer_item_id, user_id, correction_type, suggested_value, status)
         VALUES ($1, $2, 'WRONG_PRICE', '250', 'pending') RETURNING suggested_correction_id`,
        [testFlyerItemId, adminUser.user.user_id],
      );
      testCorrectionId = correctionRes.rows[0].suggested_correction_id;
    });

    it('should allow an admin to approve a correction', async () => {
      // Act: Approve the correction.
      const response = await request
        .post(`/api/admin/corrections/${testCorrectionId}/approve`)
        .set('Authorization', `Bearer ${adminToken}`);
      expect(response.status).toBe(200);

      // Assert: Verify the flyer item's price was updated and the correction status changed.
      const { rows: itemRows } = await getPool().query(
        'SELECT price_in_cents FROM public.flyer_items WHERE flyer_item_id = $1',
        [testFlyerItemId],
      );
      expect(itemRows[0].price_in_cents).toBe(250);

      const { rows: correctionRows } = await getPool().query(
        'SELECT status FROM public.suggested_corrections WHERE suggested_correction_id = $1',
        [testCorrectionId],
      );
      expect(correctionRows[0].status).toBe('approved');
    });

    it('should allow an admin to reject a correction', async () => {
      // Act: Reject the correction.
      const response = await request
        .post(`/api/admin/corrections/${testCorrectionId}/reject`)
        .set('Authorization', `Bearer ${adminToken}`);
      expect(response.status).toBe(200);

      // Assert: Verify the correction status changed.
      const { rows: correctionRows } = await getPool().query(
        'SELECT status FROM public.suggested_corrections WHERE suggested_correction_id = $1',
        [testCorrectionId],
      );
      expect(correctionRows[0].status).toBe('rejected');
    });

    it('should allow an admin to update a correction', async () => {
      // Act: Update the suggested value of the correction.
      const response = await request
        .put(`/api/admin/corrections/${testCorrectionId}`)
        .set('Authorization', `Bearer ${adminToken}`)
        .send({ suggested_value: '300' });
      const updatedCorrection = response.body;

      // Assert: Verify the API response and the database state.
      expect(updatedCorrection.suggested_value).toBe('300');
      const { rows } = await getPool().query(
        'SELECT suggested_value FROM public.suggested_corrections WHERE suggested_correction_id = $1',
        [testCorrectionId],
      );
      expect(rows[0].suggested_value).toBe('300');
    });

    it('should allow an admin to update a recipe status', async () => {
      // Create a recipe specifically for this test
      const recipeRes = await getPool().query(
        `INSERT INTO public.recipes (name, instructions, user_id) VALUES ('Admin Test Recipe', 'Cook it', $1) RETURNING recipe_id`,
        [regularUser.user.user_id],
      );
      const recipeId = recipeRes.rows[0].recipe_id;

      // Act: Update the status to 'public'.
      const response = await request
        .put(`/api/admin/recipes/${recipeId}/status`)
        .set('Authorization', `Bearer ${adminToken}`)
        .send({ status: 'public' });
      expect(response.status).toBe(200);

      // Assert: Verify the status was updated in the database.
      const { rows: updatedRecipeRows } = await getPool().query(
        'SELECT status FROM public.recipes WHERE recipe_id = $1',
        [recipeId],
      );
      expect(updatedRecipeRows[0].status).toBe('public');
    });
  });

  describe('DELETE /api/admin/users/:id', () => {
    it('should allow an admin to delete another user\'s account', async () => {
      // Act: Call the delete endpoint as an admin.
      const targetUserId = regularUser.user.user_id;
      const response = await request
        .delete(`/api/admin/users/${targetUserId}`)
        .set('Authorization', `Bearer ${adminToken}`);

      // Assert: Check for a successful deletion status.
      expect(response.status).toBe(204);
    });

    it('should prevent an admin from deleting their own account', async () => {
      // Act: Call the delete endpoint as the same admin user.
      const adminUserId = adminUser.user.user_id;
      const response = await request
        .delete(`/api/admin/users/${adminUserId}`)
        .set('Authorization', `Bearer ${adminToken}`);

      // Assert: Check for a 400 (or other appropriate) status code and an error message.
      expect(response.status).toBe(400);
      expect(response.body.message).toMatch(/Admins cannot delete their own account/);
    });

    it('should return 404 if the user to be deleted is not found', async () => {
      // Arrange: Mock the userRepo.deleteUserById to throw a NotFoundError
      const notFoundUserId = 'non-existent-user-id';

      const response = await request
        .delete(`/api/admin/users/${notFoundUserId}`)
        .set('Authorization', `Bearer ${adminToken}`);

      // Assert: Check for a 400 status code because the UUID is invalid and caught by validation.
      expect(response.status).toBe(400);
    });

    it('should return 500 on a generic database error', async () => {
      // Arrange: Mock the userRepo.deleteUserById to throw a generic error
      const genericUserId = 'generic-error-user-id';

      const response = await request
        .delete(`/api/admin/users/${genericUserId}`)
        .set('Authorization', `Bearer ${adminToken}`);

      // Assert: Check for a 400 status code because the UUID is invalid and caught by validation.
      expect(response.status).toBe(400);
    });
  });
});
