// src/tests/integration/user.routes.integration.test.ts
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import type { UserProfile } from '../../types';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';

/**
 * @vitest-environment node
 */

describe('User Routes Integration Tests (/api/users)', () => {
  let request: ReturnType<typeof supertest>;
  let authToken = '';
  let testUser: UserProfile;
  const createdUserIds: string[] = [];

  // Authenticate once before all tests in this suite to get a JWT.
  beforeAll(async () => {
    vi.stubEnv('FRONTEND_URL', 'https://example.com');
    const app = (await import('../../../server')).default;
    request = supertest(app);

    // Use the helper to create and log in a user in one step.
    const { user, token } = await createAndLoginUser({
      fullName: 'User Routes Test User',
      request,
    });
    testUser = user;
    authToken = token;
    createdUserIds.push(user.user.user_id);
  });

  afterAll(async () => {
    vi.unstubAllEnvs();
    await cleanupDb({ userIds: createdUserIds });
  });

  describe('GET /api/users/profile', () => {
    it('should return the profile for the authenticated user', async () => {
      const response = await request
        .get('/api/users/profile')
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      expect(response.body.data).toBeDefined();
      expect(response.body.data.user.email).toBe(testUser.user.email);
      expect(response.body.data.role).toBe('user');
    });

    it('should return 401 Unauthorized if no token is provided', async () => {
      const response = await request.get('/api/users/profile');
      expect(response.status).toBe(401);
    });
  });

  describe('PUT /api/users/profile', () => {
    it('should update the user profile', async () => {
      const newName = `Test User ${Date.now()}`;
      const response = await request
        .put('/api/users/profile')
        .set('Authorization', `Bearer ${authToken}`)
        .send({ full_name: newName });

      expect(response.status).toBe(200);
      expect(response.body.data.full_name).toBe(newName);

      // Verify the change by fetching the profile again
      const verifyResponse = await request
        .get('/api/users/profile')
        .set('Authorization', `Bearer ${authToken}`);

      expect(verifyResponse.body.data.full_name).toBe(newName);
    });
  });

  describe('PUT /api/users/profile/preferences', () => {
    it('should update user preferences', async () => {
      const preferences = { darkMode: true, unitSystem: 'metric' };
      const response = await request
        .put('/api/users/profile/preferences')
        .set('Authorization', `Bearer ${authToken}`)
        .send(preferences);

      expect(response.status).toBe(200);
      expect(response.body.data.preferences).toEqual(preferences);

      // Verify the change by fetching the profile again
      const verifyResponse = await request
        .get('/api/users/profile')
        .set('Authorization', `Bearer ${authToken}`);

      expect(verifyResponse.body.data.preferences?.darkMode).toBe(true);
      expect(verifyResponse.body.data.preferences?.unitSystem).toBe('metric');
    });
  });

  describe('Shopping Lists and Items', () => {
    it('should create, retrieve, and delete a shopping list', async () => {
      // 1. Create
      const listName = `My Test List - ${Date.now()}`;
      const createResponse = await request
        .post('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`)
        .send({ name: listName });

      expect(createResponse.status).toBe(201);
      expect(createResponse.body.data.name).toBe(listName);
      const listId = createResponse.body.data.shopping_list_id;
      expect(listId).toBeDefined();

      // 2. Retrieve
      const getResponse = await request
        .get('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`);

      expect(getResponse.status).toBe(200);
      const foundList = getResponse.body.data.find(
        (l: { shopping_list_id: number }) => l.shopping_list_id === listId,
      );
      expect(foundList).toBeDefined();

      // 3. Delete
      const deleteResponse = await request
        .delete(`/api/users/shopping-lists/${listId}`)
        .set('Authorization', `Bearer ${authToken}`);
      expect(deleteResponse.status).toBe(204);

      // 4. Verify Deletion
      const verifyResponse = await request
        .get('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`);
      const notFoundList = verifyResponse.body.data.find(
        (l: { shopping_list_id: number }) => l.shopping_list_id === listId,
      );
      expect(notFoundList).toBeUndefined();
    });

    it("should prevent a user from modifying another user's shopping list", async () => {
      // Arrange: Create a shopping list owned by the primary testUser.
      const listName = `Owner's List - ${Date.now()}`;
      const createListResponse = await request
        .post('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`) // Use owner's token
        .send({ name: listName });
      expect(createListResponse.status).toBe(201);
      const listId = createListResponse.body.data.shopping_list_id;

      // Arrange: Create a second, "malicious" user.
      const maliciousEmail = `malicious-user-${Date.now()}@example.com`;
      const { token: maliciousToken, user: maliciousUser } = await createAndLoginUser({
        email: maliciousEmail,
        fullName: 'Malicious User',
        request,
      });
      createdUserIds.push(maliciousUser.user.user_id); // Ensure cleanup

      // Act 1: Malicious user attempts to add an item to the owner's list.
      const addItemResponse = await request
        .post(`/api/users/shopping-lists/${listId}/items`)
        .set('Authorization', `Bearer ${maliciousToken}`) // Use malicious user's token
        .send({ customItemName: 'Malicious Item' });

      // Assert 1: The request should fail. A 404 is expected because the list is not found for this user.
      expect(addItemResponse.status).toBe(404);
      expect(addItemResponse.body.error.message).toContain('Shopping list not found');

      // Act 2: Malicious user attempts to delete the owner's list.
      const deleteResponse = await request
        .delete(`/api/users/shopping-lists/${listId}`)
        .set('Authorization', `Bearer ${maliciousToken}`); // Use malicious user's token

      // Assert 2: This should also fail with a 404.
      expect(deleteResponse.status).toBe(404);
      expect(deleteResponse.body.error.message).toContain('Shopping list not found');

      // Act 3: Malicious user attempts to update an item on the owner's list.
      // First, the owner adds an item.
      const ownerAddItemResponse = await request
        .post(`/api/users/shopping-lists/${listId}/items`)
        .set('Authorization', `Bearer ${authToken}`) // Owner's token
        .send({ customItemName: 'Legitimate Item' });
      expect(ownerAddItemResponse.status).toBe(201);
      const itemId = ownerAddItemResponse.body.data.shopping_list_item_id;

      // Now, the malicious user tries to update it.
      const updateItemResponse = await request
        .put(`/api/users/shopping-lists/items/${itemId}`)
        .set('Authorization', `Bearer ${maliciousToken}`) // Malicious token
        .send({ is_purchased: true });

      // Assert 3: This should also fail with a 404.
      expect(updateItemResponse.status).toBe(404);
      expect(updateItemResponse.body.error.message).toContain('Shopping list item not found');

      // Cleanup the list created in this test
      await request
        .delete(`/api/users/shopping-lists/${listId}`)
        .set('Authorization', `Bearer ${authToken}`);
    });
  });

  describe('Shopping List Item Management', () => {
    let listId: number;
    let itemId: number;

    // Create a dedicated list for these item tests
    beforeAll(async () => {
      const response = await request
        .post('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`)
        .send({ name: 'Item Test List' });
      listId = response.body.data.shopping_list_id;
    });

    // Clean up the list after the item tests are done
    afterAll(async () => {
      if (listId) {
        await request
          .delete(`/api/users/shopping-lists/${listId}`)
          .set('Authorization', `Bearer ${authToken}`);
      }
    });

    it('should add an item to a shopping list', async () => {
      const response = await request
        .post(`/api/users/shopping-lists/${listId}/items`)
        .set('Authorization', `Bearer ${authToken}`)
        .send({ customItemName: 'Test Item' });

      expect(response.status).toBe(201);
      expect(response.body.data.custom_item_name).toBe('Test Item');
      expect(response.body.data.shopping_list_item_id).toBeDefined();
      itemId = response.body.data.shopping_list_item_id; // Save for next tests
    });

    it('should update an item in a shopping list', async () => {
      const updates = { is_purchased: true, quantity: 5 };
      const response = await request
        .put(`/api/users/shopping-lists/items/${itemId}`)
        .set('Authorization', `Bearer ${authToken}`)
        .send(updates);

      expect(response.status).toBe(200);
      expect(response.body.data.is_purchased).toBe(true);
      expect(response.body.data.quantity).toBe(5);
    });

    it('should delete an item from a shopping list', async () => {
      const response = await request
        .delete(`/api/users/shopping-lists/items/${itemId}`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(204);
    });
  });
});
