// src/services/flyerDataTransformer.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { FlyerDataTransformer } from './flyerDataTransformer';
import { logger as mockLogger } from './logger.server';
import { generateFlyerIcon } from '../utils/imageProcessor';
import type { AiProcessorResult } from './flyerAiProcessor.server';
import { getBaseUrl } from '../utils/serverUtils';

// Mock the dependencies
vi.mock('../utils/imageProcessor', () => ({
  generateFlyerIcon: vi.fn(),
}));

vi.mock('./logger.server', () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));

vi.mock('../utils/serverUtils', () => ({
  getBaseUrl: vi.fn(),
}));

describe('FlyerDataTransformer', () => {
  let transformer: FlyerDataTransformer;

  beforeEach(() => {
    vi.clearAllMocks();
    transformer = new FlyerDataTransformer();
    // Stub environment variables to ensure consistency and predictability.
    // Prioritize FRONTEND_URL to match the updated service logic.
    vi.stubEnv('FRONTEND_URL', 'https://example.com');
    vi.stubEnv('BASE_URL', ''); // Ensure this is not used to confirm priority logic
    vi.stubEnv('PORT', ''); // Ensure this is not used

    // Provide a default mock implementation for generateFlyerIcon
    vi.mocked(generateFlyerIcon).mockResolvedValue('icon-flyer-page-1.webp');
    vi.mocked(getBaseUrl).mockReturnValue('https://example.com');
  });

  it('should transform AI data into database-ready format with a user ID', async () => {
    // Arrange
    const aiResult: AiProcessorResult = {
      data: {
        store_name: 'Test Store',
        valid_from: '2024-01-01',
        valid_to: '2024-01-07',
        store_address: '123 Test St',
        items: [
          {
            item: 'Milk',
            price_display: '$3.99',
            price_in_cents: 399,
            quantity: '1L',
            category_name: 'Dairy',
            master_item_id: 10,
          },
          {
            item: 'Bread',
            price_display: '$2.49',
            price_in_cents: 249,
            quantity: '1 loaf',
            category_name: 'Bakery',
            master_item_id: null,
          },
        ],
      },
      needsReview: false,
    };
    const originalFileName = 'my-flyer.pdf';
    const checksum = 'checksum-abc-123';
    const userId = 'user-xyz-456';
    const baseUrl = 'https://example.com';

    // Act
    const { flyerData, itemsForDb } = await transformer.transform(
      aiResult,
      originalFileName,
      'flyer-page-1.jpg',
      'icon-flyer-page-1.webp',
      checksum,
      userId,
      mockLogger,
      baseUrl,
    );

    // Assert
    // 0. Check logging
    expect(mockLogger.info).toHaveBeenCalledWith(
      'Starting data transformation from AI output to database format.',
    );
    expect(mockLogger.info).toHaveBeenCalledWith(
      { itemCount: 2, storeName: 'Test Store' },
      'Data transformation complete.',
    );

    // 1. Check flyer data
    expect(flyerData).toEqual({
      file_name: originalFileName,
      image_url: `${baseUrl}/flyer-images/flyer-page-1.jpg`,
      icon_url: `${baseUrl}/flyer-images/icons/icon-flyer-page-1.webp`,
      checksum,
      store_name: 'Test Store',
      valid_from: '2024-01-01',
      valid_to: '2024-01-07',
      store_address: '123 Test St',
      item_count: 2,
      status: 'processed',
      uploaded_by: userId,
    });

    // 2. Check item data
    expect(itemsForDb).toHaveLength(2);
    expect(itemsForDb[0]).toEqual(
      expect.objectContaining({
        item: 'Milk',
        master_item_id: 10, // Should be passed through
        view_count: 0,
        click_count: 0,
      }),
    );
    expect(itemsForDb[1]).toEqual(
      expect.objectContaining({
        item: 'Bread',
        master_item_id: undefined, // null should be converted to undefined
        view_count: 0,
        click_count: 0,
      }),
    );
  });

  it('should handle missing optional data gracefully', async () => {
    // Arrange
    const aiResult: AiProcessorResult = {
      data: {
        store_name: '', // Empty store name
        valid_from: null,
        valid_to: null,
        store_address: null,
        items: [], // No items
      },
      needsReview: true,
    };
    const originalFileName = 'another.png';
    const checksum = 'checksum-def-456';
    // No userId provided

    vi.mocked(generateFlyerIcon).mockResolvedValue('icon-another.webp');

    // Act
    const { flyerData, itemsForDb } = await transformer.transform(
      aiResult,
      originalFileName,
      'another.png',
      'icon-another.webp',
      checksum,
      undefined,
      mockLogger,
      'http://another.host',
    );

    // Assert
    // 0. Check logging
    expect(mockLogger.info).toHaveBeenCalledWith(
      'Starting data transformation from AI output to database format.',
    );
    expect(mockLogger.warn).toHaveBeenCalledWith(
      'AI did not return a store name. Using fallback "Unknown Store (auto)".',
    );
    expect(mockLogger.info).toHaveBeenCalledWith(
      { itemCount: 0, storeName: 'Unknown Store (auto)' },
      'Data transformation complete.',
    );

    expect(itemsForDb).toHaveLength(0);
    expect(flyerData).toEqual({
      file_name: originalFileName,
      image_url: `http://another.host/flyer-images/another.png`,
      icon_url: `http://another.host/flyer-images/icons/icon-another.webp`,
      checksum,
      store_name: 'Unknown Store (auto)', // Should use fallback
      valid_from: null,
      valid_to: null,
      store_address: null,
      item_count: 0,
      status: 'needs_review',
      uploaded_by: null, // Should be null
    });
  });

  it('should correctly normalize item fields with null, undefined, or empty values', async () => {
    // Arrange
    const aiResult: AiProcessorResult = {
      data: {
        store_name: 'Test Store',
        valid_from: '2024-01-01',
        valid_to: '2024-01-07',
        store_address: '123 Test St',
        items: [
          // Case 1: All fields are null or undefined
          {
            item: null,
            price_display: null,
            price_in_cents: null,
            quantity: null,
            category_name: null,
            master_item_id: null,
          },
          // Case 2: Fields are empty strings
          {
            item: '  ', // whitespace only
            price_display: '',
            price_in_cents: 200,
            quantity: '',
            category_name: '',
            master_item_id: 20,
          },
        ],
      },
      needsReview: false,
    };

    // Act
    const { itemsForDb } = await transformer.transform(
      aiResult,
      'file.pdf',
      'flyer-page-1.jpg',
      'icon-flyer-page-1.webp',
      'checksum',
      'user-1',
      mockLogger,
      'http://normalize.host',
    );

    // Assert
    expect(itemsForDb).toHaveLength(2);

    // Check Case 1 (null/undefined values)
    expect(itemsForDb[0]).toEqual(
      expect.objectContaining({
        item: 'Unknown Item',
        price_display: '',
        quantity: '',
        category_name: 'Other/Miscellaneous',
        master_item_id: undefined,
      }),
    );

    // Check Case 2 (empty string values)
    expect(itemsForDb[1]).toEqual(
      expect.objectContaining({
        item: 'Unknown Item',
        price_display: '',
        quantity: '',
        category_name: 'Other/Miscellaneous',
        master_item_id: 20,
      }),
    );
  });

  it('should use fallback baseUrl from getBaseUrl if none is provided', async () => {
    // Arrange
    const aiResult: AiProcessorResult = {
      data: {
        store_name: 'Test Store',
        valid_from: '2024-01-01',
        valid_to: '2024-01-07',
        store_address: '123 Test St',
        items: [],
      },
      needsReview: false,
    };
    const baseUrl = ''; // Explicitly pass '' for this test

    const expectedFallbackUrl = 'http://fallback-url.com';
    vi.mocked(getBaseUrl).mockReturnValue(expectedFallbackUrl);

    // Act
    const { flyerData } = await transformer.transform(
      aiResult,
      'my-flyer.pdf',
      'flyer-page-1.jpg',
      'icon-flyer-page-1.webp',
      'checksum-abc-123',
      'user-xyz-456',
      mockLogger,
      baseUrl, // Pass undefined here
    );

    // Assert
    // 1. Check that getBaseUrl was called
    expect(getBaseUrl).toHaveBeenCalledWith(mockLogger);

    // 2. Check that the URLs were constructed with the fallback
    expect(flyerData.image_url).toBe(`${expectedFallbackUrl}/flyer-images/flyer-page-1.jpg`);
    expect(flyerData.icon_url).toBe(
      `${expectedFallbackUrl}/flyer-images/icons/icon-flyer-page-1.webp`,
    );
  });

  describe('_normalizeItem price parsing', () => {
    it('should use price_in_cents from AI if it is valid, ignoring price_display', async () => {
      // Arrange
      const aiResult: AiProcessorResult = {
        data: {
          store_name: 'Test Store',
          valid_from: '2024-01-01',
          valid_to: '2024-01-07',
          store_address: '123 Test St',
          items: [
            {
              item: 'Milk',
              price_display: '$4.99', // Parsable, but should be ignored
              price_in_cents: 399, // AI provides a specific (maybe wrong) value
              quantity: '1L',
              category_name: 'Dairy',
              master_item_id: 10,
            },
          ],
        },
        needsReview: false,
      };

      // Act
      const { itemsForDb } = await transformer.transform(
        aiResult,
        'file.pdf',
        'flyer-page-1.jpg',
        'icon-flyer-page-1.webp',
        'checksum',
        'user-1',
        mockLogger,
        'http://test.host',
      );

      // Assert
      expect(itemsForDb[0].price_in_cents).toBe(399); // AI's value should be prioritized
    });

    it('should use parsePriceToCents as a fallback if AI price_in_cents is null', async () => {
      // Arrange
      const aiResult: AiProcessorResult = {
        data: {
          store_name: 'Test Store',
          valid_from: '2024-01-01',
          valid_to: '2024-01-07',
          store_address: '123 Test St',
          items: [
            {
              item: 'Milk',
              price_display: '$4.99', // Parsable value
              price_in_cents: null, // AI fails to provide a value
              quantity: '1L',
              category_name: 'Dairy',
              master_item_id: 10,
            },
          ],
        },
        needsReview: false,
      };

      // Act
      const { itemsForDb } = await transformer.transform(
        aiResult,
        'file.pdf',
        'flyer-page-1.jpg',
        'icon-flyer-page-1.webp',
        'checksum',
        'user-1',
        mockLogger,
        'http://test.host',
      );

      // Assert
      expect(itemsForDb[0].price_in_cents).toBe(499); // Should be parsed from price_display
    });

    it('should result in null if both AI price and display price are unparsable', async () => {
      // Arrange
      const aiResult: AiProcessorResult = {
        data: {
          store_name: 'Test Store',
          valid_from: '2024-01-01',
          valid_to: '2024-01-07',
          store_address: '123 Test St',
          items: [
            {
              item: 'Milk',
              price_display: 'FREE', // Unparsable
              price_in_cents: null, // AI provides null
              quantity: '1L',
              category_name: 'Dairy',
              master_item_id: 10,
            },
          ],
        },
        needsReview: false,
      };

      // Act
      const { itemsForDb } = await transformer.transform(
        aiResult,
        'file.pdf',
        'flyer-page-1.jpg',
        'icon-flyer-page-1.webp',
        'checksum',
        'user-1',
        mockLogger,
        'http://test.host',
      );

      // Assert
      expect(itemsForDb[0].price_in_cents).toBeNull();
    });
  });

  it('should handle non-string values for string fields gracefully by converting them', async () => {
    // This test verifies that if data with incorrect types bypasses earlier validation,
    // the transformer is robust enough to convert them to strings instead of crashing.
    // Arrange
    const aiResult: AiProcessorResult = {
      data: {
        store_name: 'Type-Unsafe Store',
        valid_from: '2024-01-01',
        valid_to: '2024-01-07',
        store_address: '123 Test St',
        items: [
          {
            item: 12345 as any, // Simulate AI returning a number instead of a string
            price_display: 3.99 as any, // Simulate a number for a string field
            price_in_cents: 399,
            quantity: 5 as any, // Simulate a number
            category_name: 'Dairy',
            master_item_id: 10,
          },
        ],
      },
      needsReview: false,
    };

    // Act
    const { itemsForDb } = await transformer.transform(
      aiResult,
      'file.pdf',
      'flyer-page-1.jpg',
      'icon-flyer-page-1.webp',
      'checksum',
      'user-1',
      mockLogger,
      'http://robust.host',
    );

    // Assert
    expect(itemsForDb).toHaveLength(1);
    expect(itemsForDb[0]).toEqual(
      expect.objectContaining({
        item: '12345', // Should be converted to string
        price_display: '3.99', // Should be converted to string
        quantity: '5', // Should be converted to string
      }),
    );
  });

  describe('needsReview flag handling', () => {
    it('should set status to "processed" when needsReview is false', async () => {
      // Arrange
      const aiResult: AiProcessorResult = {
        data: {
          store_name: 'Test Store',
          valid_from: '2024-01-01',
          valid_to: '2024-01-07',
          store_address: '123 Test St',
          items: [],
        },
        needsReview: false, // Key part of this test
      };

      // Act
      const { flyerData } = await transformer.transform(
        aiResult,
        'file.pdf',
        'flyer-page-1.jpg',
        'icon-flyer-page-1.webp',
        'checksum',
        'user-1',
        mockLogger,
        'http://test.host',
      );

      // Assert
      expect(flyerData.status).toBe('processed');
    });

    it('should set status to "needs_review" when needsReview is true', async () => {
      // Arrange
      const aiResult: AiProcessorResult = {
        data: {
          store_name: 'Test Store',
          valid_from: '2024-01-01',
          valid_to: '2024-01-07',
          store_address: '123 Test St',
          items: [],
        },
        needsReview: true, // Key part of this test
      };

      // Act
      const { flyerData } = await transformer.transform(
        aiResult,
        'file.pdf',
        'flyer-page-1.jpg',
        'icon-flyer-page-1.webp',
        'checksum',
        'user-1',
        mockLogger,
        'http://test.host',
      );

      // Assert
      expect(flyerData.status).toBe('needs_review');
    });
  });
});
