// src/tests/integration/edge-cases.integration.test.ts
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';

/**
 * @vitest-environment node
 *
 * Integration tests for edge cases discovered during manual frontend testing.
 * These tests cover file upload validation, input sanitization, and authorization boundaries.
 */

describe('Edge Cases Integration Tests', () => {
  let request: ReturnType<typeof supertest>;
  let authToken: string;
  let otherUserToken: string;
  const createdUserIds: string[] = [];
  const createdShoppingListIds: number[] = [];

  beforeAll(async () => {
    vi.stubEnv('FRONTEND_URL', 'https://example.com');
    const app = (await import('../../../server')).default;
    request = supertest(app);

    // Create primary test user
    const { user, token } = await createAndLoginUser({
      email: `edge-case-user-${Date.now()}@example.com`,
      fullName: 'Edge Case Test User',
      request,
    });
    authToken = token;
    createdUserIds.push(user.user.user_id);

    // Create secondary test user for cross-user tests
    const { user: user2, token: token2 } = await createAndLoginUser({
      email: `edge-case-other-${Date.now()}@example.com`,
      fullName: 'Other Test User',
      request,
    });
    otherUserToken = token2;
    createdUserIds.push(user2.user.user_id);
  });

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

  describe('File Upload Validation', () => {
    describe('Checksum Validation', () => {
      it('should reject missing checksum', async () => {
        // Create a small valid PNG
        const testImagePath = path.join(__dirname, '../assets/flyer-test.png');
        if (!fs.existsSync(testImagePath)) {
          // Skip if test asset doesn't exist
          return;
        }

        const response = await request
          .post('/api/ai/upload-and-process')
          .set('Authorization', `Bearer ${authToken}`)
          .attach('flyerFile', testImagePath);

        expect(response.status).toBe(400);
        expect(response.body.success).toBe(false);
        expect(response.body.error.message).toContain('checksum');
      });

      it('should reject invalid checksum format (non-hex)', async () => {
        const testImagePath = path.join(__dirname, '../assets/flyer-test.png');
        if (!fs.existsSync(testImagePath)) {
          return;
        }

        const response = await request
          .post('/api/ai/upload-and-process')
          .set('Authorization', `Bearer ${authToken}`)
          .attach('flyerFile', testImagePath)
          .field('checksum', 'not-a-valid-hex-checksum-at-all!!!!');

        expect(response.status).toBe(400);
        expect(response.body.success).toBe(false);
      });

      it('should reject short checksum (not 64 characters)', async () => {
        const testImagePath = path.join(__dirname, '../assets/flyer-test.png');
        if (!fs.existsSync(testImagePath)) {
          return;
        }

        const response = await request
          .post('/api/ai/upload-and-process')
          .set('Authorization', `Bearer ${authToken}`)
          .attach('flyerFile', testImagePath)
          .field('checksum', 'abc123'); // Too short

        expect(response.status).toBe(400);
        expect(response.body.success).toBe(false);
      });
    });

    describe('File Type Validation', () => {
      it('should require flyerFile field', async () => {
        const checksum = crypto.randomBytes(32).toString('hex');

        const response = await request
          .post('/api/ai/upload-and-process')
          .set('Authorization', `Bearer ${authToken}`)
          .field('checksum', checksum);

        expect(response.status).toBe(400);
        expect(response.body.success).toBe(false);
        expect(response.body.error.message).toContain('file');
      });
    });
  });

  describe('Input Sanitization', () => {
    describe('Shopping List Names', () => {
      it('should accept unicode characters and emojis', async () => {
        const response = await request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .send({ name: 'Grocery List 🛒 日本語 émoji' });

        expect(response.status).toBe(201);
        expect(response.body.success).toBe(true);
        expect(response.body.data.name).toBe('Grocery List 🛒 日本語 émoji');

        if (response.body.data.shopping_list_id) {
          createdShoppingListIds.push(response.body.data.shopping_list_id);
        }
      });

      it('should store XSS payloads as-is (frontend must escape)', async () => {
        const xssPayload = '<script>alert("xss")</script>';
        const response = await request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .send({ name: xssPayload });

        expect(response.status).toBe(201);
        expect(response.body.success).toBe(true);
        // The payload is stored as-is - frontend is responsible for escaping
        expect(response.body.data.name).toBe(xssPayload);

        if (response.body.data.shopping_list_id) {
          createdShoppingListIds.push(response.body.data.shopping_list_id);
        }
      });

      it('should reject null bytes in JSON', async () => {
        // Null bytes in JSON should be rejected by the JSON parser
        const response = await request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .set('Content-Type', 'application/json')
          .send('{"name":"test\u0000value"}');

        // JSON parser may reject this or sanitize it
        expect([400, 201]).toContain(response.status);
      });
    });
  });

  describe('Authorization Boundaries', () => {
    describe('Cross-User Resource Access', () => {
      it("should return 404 (not 403) for accessing another user's shopping list", async () => {
        // Create a shopping list as the primary user
        const createResponse = await request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .send({ name: 'Private List' });

        expect(createResponse.status).toBe(201);
        const listId = createResponse.body.data.shopping_list_id;
        createdShoppingListIds.push(listId);

        // Try to access it as the other user
        const accessResponse = await request
          .get(`/api/users/shopping-lists/${listId}`)
          .set('Authorization', `Bearer ${otherUserToken}`);

        // Should return 404 to hide resource existence
        expect(accessResponse.status).toBe(404);
        expect(accessResponse.body.success).toBe(false);
        expect(accessResponse.body.error.code).toBe('NOT_FOUND');
      });

      it("should return 404 when trying to update another user's shopping list", async () => {
        // Create a shopping list as the primary user
        const createResponse = await request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .send({ name: 'Another Private List' });

        expect(createResponse.status).toBe(201);
        const listId = createResponse.body.data.shopping_list_id;
        createdShoppingListIds.push(listId);

        // Try to update it as the other user
        const updateResponse = await request
          .put(`/api/users/shopping-lists/${listId}`)
          .set('Authorization', `Bearer ${otherUserToken}`)
          .send({ name: 'Hacked List' });

        // Should return 404 to hide resource existence
        expect(updateResponse.status).toBe(404);
      });

      it("should return 404 when trying to delete another user's shopping list", async () => {
        // Create a shopping list as the primary user
        const createResponse = await request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .send({ name: 'Delete Test List' });

        expect(createResponse.status).toBe(201);
        const listId = createResponse.body.data.shopping_list_id;
        createdShoppingListIds.push(listId);

        // Try to delete it as the other user
        const deleteResponse = await request
          .delete(`/api/users/shopping-lists/${listId}`)
          .set('Authorization', `Bearer ${otherUserToken}`);

        // Should return 404 to hide resource existence
        expect(deleteResponse.status).toBe(404);
      });
    });

    describe('SQL Injection Prevention', () => {
      it('should safely handle SQL injection in query params', async () => {
        // Attempt SQL injection in limit param
        const response = await request
          .get('/api/personalization/master-items')
          .query({ limit: '10; DROP TABLE users; --' });

        // Should either return normal data or a validation error, not crash
        expect([200, 400]).toContain(response.status);
        expect(response.body).toBeDefined();
      });

      it('should safely handle SQL injection in search params', async () => {
        // Attempt SQL injection in flyer search
        const response = await request.get('/api/flyers').query({
          search: "'; DROP TABLE flyers; --",
        });

        // Should handle safely
        expect([200, 400]).toContain(response.status);
      });
    });
  });

  describe('API Error Handling', () => {
    it('should return 404 for non-existent resources with clear message', async () => {
      const response = await request
        .get('/api/flyers/99999999')
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(404);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('NOT_FOUND');
    });

    it('should return validation error for malformed JSON body', async () => {
      const response = await request
        .post('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`)
        .set('Content-Type', 'application/json')
        .send('{ invalid json }');

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

    it('should return validation error for missing required fields', async () => {
      const response = await request
        .post('/api/budgets')
        .set('Authorization', `Bearer ${authToken}`)
        .send({}); // Empty body

      expect(response.status).toBe(400);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('VALIDATION_ERROR');
    });

    it('should return validation error for invalid data types', async () => {
      const response = await request
        .post('/api/budgets')
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          name: 'Test Budget',
          amount_cents: 'not-a-number', // Should be number
          period: 'weekly',
          start_date: '2025-01-01',
        });

      expect(response.status).toBe(400);
      expect(response.body.success).toBe(false);
    });
  });

  describe('Concurrent Operations', () => {
    it('should handle concurrent writes without data loss', async () => {
      // Create 5 shopping lists concurrently
      const promises = Array.from({ length: 5 }, (_, i) =>
        request
          .post('/api/users/shopping-lists')
          .set('Authorization', `Bearer ${authToken}`)
          .send({ name: `Concurrent List ${i + 1}` }),
      );

      const results = await Promise.all(promises);

      // All should succeed
      results.forEach((response) => {
        expect(response.status).toBe(201);
        expect(response.body.success).toBe(true);
        if (response.body.data.shopping_list_id) {
          createdShoppingListIds.push(response.body.data.shopping_list_id);
        }
      });

      // Verify all lists were created
      const listResponse = await request
        .get('/api/users/shopping-lists')
        .set('Authorization', `Bearer ${authToken}`);

      expect(listResponse.status).toBe(200);
      const lists = listResponse.body.data;
      const concurrentLists = lists.filter((l: { name: string }) =>
        l.name.startsWith('Concurrent List'),
      );
      expect(concurrentLists.length).toBe(5);
    });

    it('should handle concurrent reads without errors', async () => {
      // Make 10 concurrent read requests
      const promises = Array.from({ length: 10 }, () =>
        request.get('/api/personalization/master-items'),
      );

      const results = await Promise.all(promises);

      // All should succeed
      results.forEach((response) => {
        expect(response.status).toBe(200);
        expect(response.body.success).toBe(true);
      });
    });
  });
});
