// src/tests/integration/receipt.integration.test.ts
/**
 * Integration tests for Receipt processing workflow.
 * Tests the complete flow from receipt upload to item extraction and inventory addition.
 */
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';
import { getPool } from '../../services/db/connection.db';

/**
 * @vitest-environment node
 */

// Mock Bull Board to prevent BullMQAdapter from validating queue instances
vi.mock('@bull-board/api', () => ({
  createBullBoard: vi.fn(),
}));
vi.mock('@bull-board/api/bullMQAdapter', () => ({
  BullMQAdapter: vi.fn(),
}));

// Mock the queues to prevent actual background processing
// IMPORTANT: Must include all queue exports that are imported by workers.server.ts
vi.mock('../../services/queues.server', () => ({
  receiptQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-job-id' }),
  },
  cleanupQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-cleanup-job-id' }),
  },
  flyerQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-flyer-job-id' }),
  },
  emailQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-email-job-id' }),
  },
  analyticsQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-analytics-job-id' }),
  },
  weeklyAnalyticsQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-weekly-analytics-job-id' }),
  },
  tokenCleanupQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-token-cleanup-job-id' }),
  },
  expiryAlertQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-expiry-alert-job-id' }),
  },
  barcodeDetectionQueue: {
    add: vi.fn().mockResolvedValue({ id: 'mock-barcode-job-id' }),
  },
}));

describe('Receipt Processing Integration Tests (/api/receipts)', () => {
  let request: ReturnType<typeof supertest>;
  let authToken = '';
  let testUser: UserProfile;
  const createdUserIds: string[] = [];
  const createdReceiptIds: number[] = [];
  const createdInventoryIds: number[] = [];

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

    // Create a user for receipt tests
    const { user, token } = await createAndLoginUser({
      email: `receipt-test-user-${Date.now()}@example.com`,
      fullName: 'Receipt Test User',
      request,
    });
    testUser = user;
    authToken = token;
    createdUserIds.push(user.user.user_id);
  });

  afterAll(async () => {
    vi.unstubAllEnvs();

    const pool = getPool();

    // Clean up inventory items
    if (createdInventoryIds.length > 0) {
      await pool.query('DELETE FROM public.user_inventory WHERE inventory_id = ANY($1::int[])', [
        createdInventoryIds,
      ]);
    }

    // Clean up receipt items and receipts
    if (createdReceiptIds.length > 0) {
      await pool.query('DELETE FROM public.receipt_items WHERE receipt_id = ANY($1::int[])', [
        createdReceiptIds,
      ]);
      await pool.query(
        'DELETE FROM public.receipt_processing_log WHERE receipt_id = ANY($1::int[])',
        [createdReceiptIds],
      );
      await pool.query('DELETE FROM public.receipts WHERE receipt_id = ANY($1::int[])', [
        createdReceiptIds,
      ]);
    }

    await cleanupDb({ userIds: createdUserIds });
  });

  describe('POST /api/receipts - Upload Receipt', () => {
    it('should upload a receipt image successfully', async () => {
      // Create a simple test image buffer
      const testImageBuffer = Buffer.from(
        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
        'base64',
      );

      const response = await request
        .post('/api/receipts')
        .set('Authorization', `Bearer ${authToken}`)
        .attach('receipt', testImageBuffer, 'test-receipt.png')
        .field('store_id', '1')
        .field('transaction_date', '2024-01-15');

      expect(response.status).toBe(201);
      expect(response.body.success).toBe(true);
      expect(response.body.data.receipt_id).toBeDefined();
      expect(response.body.data.job_id).toBe('mock-job-id');

      createdReceiptIds.push(response.body.data.receipt_id);
    });

    it('should upload receipt without optional fields', async () => {
      const testImageBuffer = Buffer.from(
        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
        'base64',
      );

      const response = await request
        .post('/api/receipts')
        .set('Authorization', `Bearer ${authToken}`)
        .attach('receipt', testImageBuffer, 'test-receipt-2.png');

      expect(response.status).toBe(201);
      expect(response.body.data.receipt_id).toBeDefined();

      createdReceiptIds.push(response.body.data.receipt_id);
    });

    it('should reject request without file', async () => {
      const response = await request
        .post('/api/receipts')
        .set('Authorization', `Bearer ${authToken}`);

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

    it('should reject unauthenticated requests', async () => {
      const testImageBuffer = Buffer.from(
        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
        'base64',
      );

      const response = await request
        .post('/api/receipts')
        .attach('receipt', testImageBuffer, 'test-receipt.png');

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

  describe('GET /api/receipts - List Receipts', () => {
    beforeAll(async () => {
      // Create some receipts for testing
      const pool = getPool();
      for (let i = 0; i < 3; i++) {
        const result = await pool.query(
          `INSERT INTO public.receipts (user_id, receipt_image_url, status)
           VALUES ($1, $2, $3)
           RETURNING receipt_id`,
          [
            testUser.user.user_id,
            `/uploads/receipts/test-${i}.jpg`,
            i === 0 ? 'completed' : 'pending',
          ],
        );
        createdReceiptIds.push(result.rows[0].receipt_id);
      }
    });

    it('should return paginated list of receipts', async () => {
      const response = await request
        .get('/api/receipts')
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.receipts).toBeDefined();
      expect(Array.isArray(response.body.data.receipts)).toBe(true);
      expect(response.body.data.total).toBeGreaterThanOrEqual(3);
    });

    it('should support status filter', async () => {
      const response = await request
        .get('/api/receipts')
        .query({ status: 'completed' })
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      response.body.data.receipts.forEach((receipt: { status: string }) => {
        expect(receipt.status).toBe('completed');
      });
    });

    it('should support pagination', async () => {
      const response = await request
        .get('/api/receipts')
        .query({ limit: 2, offset: 0 })
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      expect(response.body.data.receipts.length).toBeLessThanOrEqual(2);
    });

    it('should only return receipts for the authenticated user', async () => {
      // Create another user
      const { user: otherUser, token: otherToken } = await createAndLoginUser({
        email: `other-receipt-user-${Date.now()}@example.com`,
        fullName: 'Other Receipt User',
        request,
      });
      createdUserIds.push(otherUser.user.user_id);

      const response = await request
        .get('/api/receipts')
        .set('Authorization', `Bearer ${otherToken}`);

      expect(response.status).toBe(200);
      // Other user should have no receipts
      expect(response.body.data.total).toBe(0);
    });
  });

  describe('GET /api/receipts/:receiptId - Get Receipt Details', () => {
    let testReceiptId: number;

    beforeAll(async () => {
      const pool = getPool();

      // First create or get a test store
      const storeResult = await pool.query(
        `INSERT INTO public.stores (name)
         VALUES ('Test Store')
         ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
         RETURNING store_id`,
      );
      const storeId = storeResult.rows[0].store_id;

      const result = await pool.query(
        `INSERT INTO public.receipts (user_id, receipt_image_url, status, store_id, total_amount_cents)
         VALUES ($1, $2, 'completed', $3, 9999)
         RETURNING receipt_id`,
        [testUser.user.user_id, '/uploads/receipts/detail-test.jpg', storeId],
      );
      testReceiptId = result.rows[0].receipt_id;
      createdReceiptIds.push(testReceiptId);

      // Add some items to the receipt
      await pool.query(
        `INSERT INTO public.receipt_items (receipt_id, raw_item_description, quantity, price_paid_cents, status)
         VALUES ($1, 'MILK 2% 4L', 1, 599, 'matched'),
                ($1, 'BREAD WHITE', 2, 498, 'unmatched')`,
        [testReceiptId],
      );
    });

    it('should return receipt with items', async () => {
      const response = await request
        .get(`/api/receipts/${testReceiptId}`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.receipt).toBeDefined();
      expect(response.body.data.receipt.receipt_id).toBe(testReceiptId);
      expect(response.body.data.receipt.store_id).toBeDefined();
      expect(response.body.data.items).toBeDefined();
      expect(response.body.data.items.length).toBe(2);
    });

    it('should return 404 for non-existent receipt', async () => {
      const response = await request
        .get('/api/receipts/999999')
        .set('Authorization', `Bearer ${authToken}`);

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

    it("should not allow accessing another user's receipt", async () => {
      const { user: otherUser, token: otherToken } = await createAndLoginUser({
        email: `receipt-access-test-${Date.now()}@example.com`,
        fullName: 'Receipt Access Test',
        request,
      });
      createdUserIds.push(otherUser.user.user_id);

      const response = await request
        .get(`/api/receipts/${testReceiptId}`)
        .set('Authorization', `Bearer ${otherToken}`);

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

  describe('DELETE /api/receipts/:receiptId - Delete Receipt', () => {
    it('should delete a receipt', async () => {
      // Create a receipt to delete
      const pool = getPool();
      const result = await pool.query(
        `INSERT INTO public.receipts (user_id, receipt_image_url, status)
         VALUES ($1, '/uploads/receipts/delete-test.jpg', 'pending')
         RETURNING receipt_id`,
        [testUser.user.user_id],
      );
      const receiptId = result.rows[0].receipt_id;

      const response = await request
        .delete(`/api/receipts/${receiptId}`)
        .set('Authorization', `Bearer ${authToken}`);

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

      // Verify deletion
      const verifyResponse = await request
        .get(`/api/receipts/${receiptId}`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(verifyResponse.status).toBe(404);
    });
  });

  describe('POST /api/receipts/:receiptId/reprocess - Reprocess Receipt', () => {
    let failedReceiptId: number;

    beforeAll(async () => {
      const pool = getPool();
      const result = await pool.query(
        `INSERT INTO public.receipts (user_id, receipt_image_url, status, error_details)
         VALUES ($1, '/uploads/receipts/failed-test.jpg', 'failed', '{"message": "OCR failed"}'::jsonb)
         RETURNING receipt_id`,
        [testUser.user.user_id],
      );
      failedReceiptId = result.rows[0].receipt_id;
      createdReceiptIds.push(failedReceiptId);
    });

    it('should queue a failed receipt for reprocessing', async () => {
      const response = await request
        .post(`/api/receipts/${failedReceiptId}/reprocess`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.message).toContain('reprocessing');
      expect(response.body.data.job_id).toBe('mock-job-id');
    });

    it('should return 404 for non-existent receipt', async () => {
      const response = await request
        .post('/api/receipts/999999/reprocess')
        .set('Authorization', `Bearer ${authToken}`);

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

  describe('Receipt Items Management', () => {
    let receiptWithItemsId: number;
    let testItemId: number;

    beforeAll(async () => {
      const pool = getPool();
      const receiptResult = await pool.query(
        `INSERT INTO public.receipts (user_id, receipt_image_url, status)
         VALUES ($1, '/uploads/receipts/items-test.jpg', 'completed')
         RETURNING receipt_id`,
        [testUser.user.user_id],
      );
      receiptWithItemsId = receiptResult.rows[0].receipt_id;
      createdReceiptIds.push(receiptWithItemsId);

      const itemResult = await pool.query(
        `INSERT INTO public.receipt_items (receipt_id, raw_item_description, quantity, price_paid_cents, status)
         VALUES ($1, 'EGGS LARGE 12CT', 1, 499, 'unmatched')
         RETURNING receipt_item_id`,
        [receiptWithItemsId],
      );
      testItemId = itemResult.rows[0].receipt_item_id;
    });

    describe('GET /api/receipts/:receiptId/items', () => {
      it('should return all receipt items', async () => {
        const response = await request
          .get(`/api/receipts/${receiptWithItemsId}/items`)
          .set('Authorization', `Bearer ${authToken}`);

        expect(response.status).toBe(200);
        expect(response.body.data.items).toBeDefined();
        expect(response.body.data.items.length).toBeGreaterThanOrEqual(1);
        expect(response.body.data.total).toBeGreaterThanOrEqual(1);
      });
    });

    describe('PUT /api/receipts/:receiptId/items/:itemId', () => {
      it('should update item status', async () => {
        const response = await request
          .put(`/api/receipts/${receiptWithItemsId}/items/${testItemId}`)
          .set('Authorization', `Bearer ${authToken}`)
          .send({ status: 'matched', match_confidence: 0.95 });

        expect(response.status).toBe(200);
        expect(response.body.data.status).toBe('matched');
      });

      it('should reject invalid status', async () => {
        const response = await request
          .put(`/api/receipts/${receiptWithItemsId}/items/${testItemId}`)
          .set('Authorization', `Bearer ${authToken}`)
          .send({ status: 'invalid_status' });

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

    describe('GET /api/receipts/:receiptId/items/unadded', () => {
      it('should return unadded items', async () => {
        const response = await request
          .get(`/api/receipts/${receiptWithItemsId}/items/unadded`)
          .set('Authorization', `Bearer ${authToken}`);

        expect(response.status).toBe(200);
        expect(response.body.data.items).toBeDefined();
        expect(Array.isArray(response.body.data.items)).toBe(true);
      });
    });
  });

  describe('POST /api/receipts/:receiptId/confirm - Confirm Items to Inventory', () => {
    let receiptForConfirmId: number;
    let itemToConfirmId: number;

    beforeAll(async () => {
      const pool = getPool();
      const receiptResult = await pool.query(
        `INSERT INTO public.receipts (user_id, receipt_image_url, status)
         VALUES ($1, '/uploads/receipts/confirm-test.jpg', 'completed')
         RETURNING receipt_id`,
        [testUser.user.user_id],
      );
      receiptForConfirmId = receiptResult.rows[0].receipt_id;
      createdReceiptIds.push(receiptForConfirmId);

      const itemResult = await pool.query(
        `INSERT INTO public.receipt_items (receipt_id, raw_item_description, quantity, price_paid_cents, status, added_to_pantry)
         VALUES ($1, 'YOGURT GREEK', 2, 798, 'matched', false)
         RETURNING receipt_item_id`,
        [receiptForConfirmId],
      );
      itemToConfirmId = itemResult.rows[0].receipt_item_id;
    });

    it('should confirm items and add to inventory', async () => {
      const response = await request
        .post(`/api/receipts/${receiptForConfirmId}/confirm`)
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          items: [
            {
              receipt_item_id: itemToConfirmId,
              include: true,
              item_name: 'Greek Yogurt',
              quantity: 2,
              location: 'fridge',
              expiry_date: '2024-02-15',
            },
          ],
        });

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.added_items).toBeDefined();
      expect(response.body.data.count).toBeGreaterThanOrEqual(0);

      // Track created inventory items for cleanup
      if (response.body.data.added_items) {
        response.body.data.added_items.forEach((item: { inventory_id: number }) => {
          if (item.inventory_id) {
            createdInventoryIds.push(item.inventory_id);
          }
        });
      }
    });

    it('should skip items with include: false', async () => {
      const pool = getPool();
      const itemResult = await pool.query(
        `INSERT INTO public.receipt_items (receipt_id, raw_item_description, quantity, price_paid_cents, status, added_to_pantry)
         VALUES ($1, 'CHIPS BBQ', 1, 499, 'matched', false)
         RETURNING receipt_item_id`,
        [receiptForConfirmId],
      );
      const skipItemId = itemResult.rows[0].receipt_item_id;

      const response = await request
        .post(`/api/receipts/${receiptForConfirmId}/confirm`)
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          items: [
            {
              receipt_item_id: skipItemId,
              include: false,
            },
          ],
        });

      expect(response.status).toBe(200);
      // No items should be added when all are excluded
    });

    it('should reject invalid location', async () => {
      const response = await request
        .post(`/api/receipts/${receiptForConfirmId}/confirm`)
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          items: [
            {
              receipt_item_id: itemToConfirmId,
              include: true,
              location: 'invalid_location',
            },
          ],
        });

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

  describe('GET /api/receipts/:receiptId/logs - Processing Logs', () => {
    let receiptWithLogsId: number;

    beforeAll(async () => {
      const pool = getPool();
      const receiptResult = await pool.query(
        `INSERT INTO public.receipts (user_id, receipt_image_url, status)
         VALUES ($1, '/uploads/receipts/logs-test.jpg', 'completed')
         RETURNING receipt_id`,
        [testUser.user.user_id],
      );
      receiptWithLogsId = receiptResult.rows[0].receipt_id;
      createdReceiptIds.push(receiptWithLogsId);

      // Add processing logs - using correct table name and column names
      // processing_step must be one of: upload, ocr_extraction, text_parsing, store_detection,
      // item_extraction, item_matching, price_parsing, finalization
      await pool.query(
        `INSERT INTO public.receipt_processing_log (receipt_id, processing_step, status, error_message)
         VALUES ($1, 'ocr_extraction', 'completed', 'OCR completed successfully'),
                ($1, 'item_extraction', 'completed', 'Extracted 5 items'),
                ($1, 'item_matching', 'completed', 'Matched 3 items')`,
        [receiptWithLogsId],
      );
    });

    it('should return processing logs', async () => {
      const response = await request
        .get(`/api/receipts/${receiptWithLogsId}/logs`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.logs).toBeDefined();
      expect(response.body.data.logs.length).toBe(3);
      expect(response.body.data.total).toBe(3);
    });
  });

  describe('Complete Receipt Workflow', () => {
    it('should handle full upload-process-confirm workflow', async () => {
      // Note: Full workflow with actual processing would require BullMQ worker
      // This test verifies the API contract works correctly

      // Step 1: Upload receipt
      const testImageBuffer = Buffer.from(
        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
        'base64',
      );

      const uploadResponse = await request
        .post('/api/receipts')
        .set('Authorization', `Bearer ${authToken}`)
        .attach('receipt', testImageBuffer, 'workflow-test.png')
        .field('transaction_date', '2024-01-20');

      expect(uploadResponse.status).toBe(201);
      const receiptId = uploadResponse.body.data.receipt_id;
      createdReceiptIds.push(receiptId);

      // Step 2: Verify receipt was created
      const getResponse = await request
        .get(`/api/receipts/${receiptId}`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(getResponse.status).toBe(200);
      expect(getResponse.body.data.receipt.receipt_id).toBe(receiptId);

      // Step 3: Check it appears in list
      const listResponse = await request
        .get('/api/receipts')
        .set('Authorization', `Bearer ${authToken}`);

      expect(listResponse.status).toBe(200);
      const found = listResponse.body.data.receipts.find(
        (r: { receipt_id: number }) => r.receipt_id === receiptId,
      );
      expect(found).toBeDefined();

      // Step 4: Verify logs endpoint works (empty for new receipt)
      const logsResponse = await request
        .get(`/api/receipts/${receiptId}/logs`)
        .set('Authorization', `Bearer ${authToken}`);

      expect(logsResponse.status).toBe(200);
      expect(Array.isArray(logsResponse.body.data.logs)).toBe(true);
    });
  });
});
