// src/tests/e2e/upc-journey.e2e.test.ts
/**
 * End-to-End test for the UPC scanning user journey.
 * Tests the complete flow from user registration to scanning UPCs and viewing history.
 */
import { describe, it, expect, afterAll } from 'vitest';
import * as apiClient from '../../services/apiClient';
import { cleanupDb } from '../utils/cleanup';
import { poll } from '../utils/poll';
import { getPool } from '../../services/db/connection.db';

/**
 * @vitest-environment node
 */

const API_BASE_URL = process.env.VITE_API_BASE_URL || 'http://localhost:3000/api';

// Helper to make authenticated API calls
const authedFetch = async (
  path: string,
  options: RequestInit & { token?: string } = {},
): Promise<Response> => {
  const { token, ...fetchOptions } = options;
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(fetchOptions.headers as Record<string, string>),
  };

  if (token) {
    headers['Authorization'] = `Bearer ${token}`;
  }

  return fetch(`${API_BASE_URL}${path}`, {
    ...fetchOptions,
    headers,
  });
};

describe('E2E UPC Scanning Journey', () => {
  const uniqueId = Date.now();
  const userEmail = `upc-e2e-${uniqueId}@example.com`;
  const userPassword = 'StrongUpcPassword123!';

  let authToken: string;
  let userId: string | null = null;
  const createdScanIds: number[] = [];
  const createdProductIds: number[] = [];

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

    // Clean up scan history
    if (createdScanIds.length > 0) {
      await pool.query('DELETE FROM public.upc_scan_history WHERE scan_id = ANY($1::int[])', [
        createdScanIds,
      ]);
    }

    // Clean up test products
    if (createdProductIds.length > 0) {
      await pool.query('DELETE FROM public.products WHERE product_id = ANY($1::int[])', [
        createdProductIds,
      ]);
    }

    // Clean up user
    await cleanupDb({
      userIds: [userId],
    });
  });

  it('should complete full UPC scanning journey: Register -> Scan -> Lookup -> History -> Stats', async () => {
    // Step 1: Register a new user
    const registerResponse = await apiClient.registerUser(userEmail, userPassword, 'UPC E2E User');
    expect(registerResponse.status).toBe(201);

    // Step 2: Login to get auth token
    const { response: loginResponse, responseBody: loginResponseBody } = await poll(
      async () => {
        const response = await apiClient.loginUser(userEmail, userPassword, false);
        const responseBody = response.ok ? await response.clone().json() : {};
        return { response, responseBody };
      },
      (result) => result.response.ok,
      { timeout: 10000, interval: 1000, description: 'user login after registration' },
    );

    expect(loginResponse.status).toBe(200);
    authToken = loginResponseBody.data.token;
    userId = loginResponseBody.data.userprofile.user.user_id;
    expect(authToken).toBeDefined();

    // Step 3: Create a test product with UPC in the database
    // Products table requires master_item_id (FK to master_grocery_items), has optional brand_id
    const pool = getPool();
    const testUpc = `${Date.now()}`.slice(-12).padStart(12, '0');

    // First, create or get a master grocery item
    const masterItemResult = await pool.query(
      `INSERT INTO public.master_grocery_items (name)
       VALUES ('E2E Test Product Item')
       ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
       RETURNING master_grocery_item_id`,
    );
    const masterItemId = masterItemResult.rows[0].master_grocery_item_id;

    const productResult = await pool.query(
      `INSERT INTO public.products (name, master_item_id, upc_code, description)
       VALUES ('E2E Test Product', $1, $2, 'Product for E2E testing')
       RETURNING product_id`,
      [masterItemId, testUpc],
    );
    const productId = productResult.rows[0].product_id;
    createdProductIds.push(productId);

    // Step 4: Scan the UPC code
    const scanResponse = await authedFetch('/upc/scan', {
      method: 'POST',
      token: authToken,
      body: JSON.stringify({
        upc_code: testUpc,
        scan_source: 'manual_entry',
      }),
    });

    expect(scanResponse.status).toBe(200);
    const scanData = await scanResponse.json();
    expect(scanData.success).toBe(true);
    expect(scanData.data.upc_code).toBe(testUpc);
    const scanId = scanData.data.scan_id;
    createdScanIds.push(scanId);

    // Step 5: Lookup the product by UPC
    const lookupResponse = await authedFetch(`/upc/lookup?upc_code=${testUpc}`, {
      method: 'GET',
      token: authToken,
    });

    expect(lookupResponse.status).toBe(200);
    const lookupData = await lookupResponse.json();
    expect(lookupData.success).toBe(true);
    expect(lookupData.data.product).toBeDefined();
    expect(lookupData.data.product.name).toBe('E2E Test Product');

    // Step 6: Scan a few more items to build history
    for (let i = 0; i < 3; i++) {
      const additionalScan = await authedFetch('/upc/scan', {
        method: 'POST',
        token: authToken,
        body: JSON.stringify({
          upc_code: `00000000000${i}`,
          scan_source: i % 2 === 0 ? 'manual_entry' : 'image_upload',
        }),
      });

      if (additionalScan.ok) {
        const additionalData = await additionalScan.json();
        if (additionalData.data?.scan_id) {
          createdScanIds.push(additionalData.data.scan_id);
        }
      }
    }

    // Step 7: View scan history
    const historyResponse = await authedFetch('/upc/history', {
      method: 'GET',
      token: authToken,
    });

    expect(historyResponse.status).toBe(200);
    const historyData = await historyResponse.json();
    expect(historyData.success).toBe(true);
    expect(historyData.data.scans.length).toBeGreaterThanOrEqual(4); // At least our 4 scans
    expect(historyData.data.total).toBeGreaterThanOrEqual(4);

    // Step 8: View specific scan details
    const scanDetailResponse = await authedFetch(`/upc/history/${scanId}`, {
      method: 'GET',
      token: authToken,
    });

    expect(scanDetailResponse.status).toBe(200);
    const scanDetailData = await scanDetailResponse.json();
    expect(scanDetailData.data.scan_id).toBe(scanId);
    expect(scanDetailData.data.upc_code).toBe(testUpc);

    // Step 9: Check user scan statistics
    const statsResponse = await authedFetch('/upc/stats', {
      method: 'GET',
      token: authToken,
    });

    expect(statsResponse.status).toBe(200);
    const statsData = await statsResponse.json();
    expect(statsData.success).toBe(true);
    expect(statsData.data.total_scans).toBeGreaterThanOrEqual(4);

    // Step 10: Test history filtering by scan_source
    const filteredHistoryResponse = await authedFetch('/upc/history?scan_source=manual_entry', {
      method: 'GET',
      token: authToken,
    });

    expect(filteredHistoryResponse.status).toBe(200);
    const filteredData = await filteredHistoryResponse.json();
    filteredData.data.scans.forEach((scan: { scan_source: string }) => {
      expect(scan.scan_source).toBe('manual_entry');
    });

    // Step 11: Verify another user cannot see our scans
    const otherUserEmail = `other-upc-e2e-${uniqueId}@example.com`;
    await apiClient.registerUser(otherUserEmail, userPassword, 'Other UPC User');

    const { responseBody: otherLoginData } = await poll(
      async () => {
        const response = await apiClient.loginUser(otherUserEmail, userPassword, false);
        const responseBody = response.ok ? await response.clone().json() : {};
        return { response, responseBody };
      },
      (result) => result.response.ok,
      { timeout: 10000, interval: 1000, description: 'other user login' },
    );

    const otherToken = otherLoginData.data.token;
    const otherUserId = otherLoginData.data.userprofile.user.user_id;

    // Other user should not see our scan
    const otherScanDetailResponse = await authedFetch(`/upc/history/${scanId}`, {
      method: 'GET',
      token: otherToken,
    });

    expect(otherScanDetailResponse.status).toBe(404);

    // Other user's history should be empty
    const otherHistoryResponse = await authedFetch('/upc/history', {
      method: 'GET',
      token: otherToken,
    });

    expect(otherHistoryResponse.status).toBe(200);
    const otherHistoryData = await otherHistoryResponse.json();
    expect(otherHistoryData.data.total).toBe(0);

    // Clean up other user
    await cleanupDb({ userIds: [otherUserId] });

    // Step 12: Delete account (self-service)
    const deleteAccountResponse = await apiClient.deleteUserAccount(userPassword, {
      tokenOverride: authToken,
    });

    expect(deleteAccountResponse.status).toBe(200);

    // Mark userId as null to avoid double deletion in afterAll
    userId = null;
  });
});
