import { describe, it, expect } from 'vitest';
import { parseCsv, MAX_FILE_SIZE, MAX_ROWS } from '../../../src/modules/products/csv-parser.js';
import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared';

describe('parseCsv', () => {
  it('parses a valid CSV with all columns', () => {
    const csv = [
      'name,brand,barcode,category,servingSize,servingUnit,densityGPerMl,calories,protein,carbs,fat,fiber,sugar,sodium,saturatedFat,cholesterol,tags',
      'Chicken Breast,Tyson,1234567890123,meat,100,g,,165,31,0,3.6,0,0,74,1,85,protein;lean',
    ].join('\n');

    const result = parseCsv(csv);

    expect(result.errors).toHaveLength(0);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]).toMatchObject({
      name: 'Chicken Breast',
      brand: 'Tyson',
      barcode: '1234567890123',
      category: ProductCategory.MEAT,
      servingSize: 100,
      servingUnit: ServingUnit.GRAMS,
      nutrition: { calories: 165, protein: 31, carbs: 0, fat: 3.6 },
      tags: ['protein', 'lean'],
      source: ProductSource.IMPORT,
    });
  });

  it('handles minimal CSV with only name column', () => {
    const csv = 'name\nRice\nBeans';
    const result = parseCsv(csv);

    expect(result.errors).toHaveLength(0);
    expect(result.items).toHaveLength(2);
    expect(result.items[0]!.name).toBe('Rice');
    expect(result.items[0]!.category).toBe(ProductCategory.OTHER);
    expect(result.items[0]!.servingUnit).toBe(ServingUnit.GRAMS);
    expect(result.items[0]!.servingSize).toBe(100);
  });

  it('returns error for empty file', () => {
    const result = parseCsv('');
    expect(result.items).toHaveLength(0);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]!.message).toBe('Empty file');
  });

  it('returns error when name column is missing', () => {
    const csv = 'brand,category\nNikko,meat';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(0);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]!.message).toContain('Missing required "name" column');
  });

  it('skips rows with empty name', () => {
    const csv = 'name,category\n,meat\nChicken,meat';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]!.message).toContain('Missing required field: name');
  });

  it('rejects invalid servingUnit', () => {
    const csv = 'name,servingUnit\nFlour,cup';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(0);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]!.message).toContain('Invalid servingUnit');
    expect(result.errors[0]!.message).toContain('cup');
  });

  it('rejects negative servingSize', () => {
    const csv = 'name,servingSize\nBad,-10';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(0);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]!.message).toContain('servingSize must be a positive number');
  });

  it('handles quoted fields with commas', () => {
    const csv = 'name,brand\n"Peanut Butter, Crunchy",Jif';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]!.name).toBe('Peanut Butter, Crunchy');
    expect(result.items[0]!.brand).toBe('Jif');
  });

  it('handles escaped quotes in CSV', () => {
    const csv = 'name,brand\n"8"" Pizza",DiGiorno';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]!.name).toBe('8" Pizza');
  });

  it('uses ml serving unit when specified', () => {
    const csv = 'name,servingUnit\nMilk,ml';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]!.servingUnit).toBe(ServingUnit.MILLILITERS);
  });

  it('includes densityGPerMl when provided', () => {
    const csv = 'name,densityGPerMl\nOlive Oil,0.92';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]!.densityGPerMl).toBe(0.92);
  });

  it('parses optional nutrition fields', () => {
    const csv =
      'name,calories,protein,carbs,fat,fiber,sugar,sodium,saturatedFat,cholesterol\nEgg,155,13,1.1,11,0,1.1,124,3.3,373';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]!.nutrition).toEqual({
      calories: 155,
      protein: 13,
      carbs: 1.1,
      fat: 11,
      fiber: 0,
      sugar: 1.1,
      sodium: 124,
      saturatedFat: 3.3,
      cholesterol: 373,
    });
  });

  it('handles Windows line endings (CRLF)', () => {
    const csv = 'name,category\r\nApple,fruits\r\nBanana,fruits';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(2);
  });

  it('ignores blank lines', () => {
    const csv = 'name\n\nApple\n\nBanana\n';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(2);
  });

  it('maps valid category strings', () => {
    const csv = 'name,category\nYogurt,dairy\nSalmon,seafood';
    const result = parseCsv(csv);
    expect(result.items[0]!.category).toBe(ProductCategory.DAIRY);
    expect(result.items[1]!.category).toBe(ProductCategory.SEAFOOD);
  });

  it('defaults invalid category to OTHER', () => {
    const csv = 'name,category\nMystery,invalid_cat';
    const result = parseCsv(csv);
    expect(result.items[0]!.category).toBe(ProductCategory.OTHER);
  });

  it('exports MAX_FILE_SIZE and MAX_ROWS constants', () => {
    expect(MAX_FILE_SIZE).toBe(5 * 1024 * 1024);
    expect(MAX_ROWS).toBe(5000);
  });

  it('handles non-numeric servingSize as error', () => {
    const csv = 'name,servingSize\nBad,abc';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(0);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]!.message).toContain('servingSize must be a positive number');
  });

  it('handles case-insensitive headers', () => {
    const csv = 'Name,Brand,Category,ServingSize,ServingUnit\nTest,Brand1,meat,50,g';
    const result = parseCsv(csv);
    expect(result.items).toHaveLength(1);
    expect(result.items[0]!.name).toBe('Test');
    expect(result.items[0]!.brand).toBe('Brand1');
  });
});
