'use strict';

const {
  stripMetaFields,
  buildIdMap,
  withConnection,
  simulateInsertedRows,
  stripForComparison,
  compareLevel,
  buildResponse,
  SIMULATED_ID_SEEDS,
} = require('../../api/utils/duplicatedMenuUtils');

describe('stripMetaFields', () => {
  it('elimina id, createdAt, updatedAt, created_at, updated_at', () => {
    const row = { id: 1, name: 'Tacos', createdAt: 'x', updatedAt: 'y', created_at: 'a', updated_at: 'b' };
    expect(stripMetaFields(row)).toEqual({ name: 'Tacos' });
  });

  it('aplica overrides sobre el resultado', () => {
    const row = { id: 1, restaurant_id: 10, name: 'Pizza' };
    expect(stripMetaFields(row, { restaurant_id: 99 })).toEqual({ restaurant_id: 99, name: 'Pizza' });
  });

  it('funciona con row sin campos meta', () => {
    const row = { name: 'Burger', price: 5 };
    expect(stripMetaFields(row)).toEqual({ name: 'Burger', price: 5 });
  });
});

describe('buildIdMap', () => {
  it('construye mapa oldId → newId correctamente', () => {
    const src = [{ id: 1 }, { id: 2 }];
    const ins = [{ id: 10 }, { id: 20 }];
    const map = buildIdMap(src, ins);
    expect(map.get(1)).toBe(10);
    expect(map.get(2)).toBe(20);
  });

  it('lanza DuplicatedMenuError si los arrays tienen distinto tamaño', () => {
    expect(() => buildIdMap([{ id: 1 }], [])).toThrow('Inconsistencia al clonar');
  });

  it('devuelve Map vacío para arrays vacíos', () => {
    const map = buildIdMap([], []);
    expect(map.size).toBe(0);
  });
});

describe('withConnection', () => {
  it('llama usingConnection cuando hay db', () => {
    const usingConnection = jest.fn().mockReturnValue('connected');
    const deferred = { usingConnection };
    expect(withConnection(deferred, 'db')).toBe('connected');
    expect(usingConnection).toHaveBeenCalledWith('db');
  });

  it('devuelve el deferred sin modificar cuando db es null', () => {
    const deferred = { usingConnection: jest.fn() };
    expect(withConnection(deferred, null)).toBe(deferred);
    expect(deferred.usingConnection).not.toHaveBeenCalled();
  });

  it('devuelve el deferred sin modificar cuando db es undefined', () => {
    const deferred = { usingConnection: jest.fn() };
    expect(withConnection(deferred, undefined)).toBe(deferred);
  });
});

describe('simulateInsertedRows', () => {
  it('asigna ids negativos basados en el seed', () => {
    const rows = [{ name: 'A' }, { name: 'B' }];
    const result = simulateInsertedRows(rows, SIMULATED_ID_SEEDS.menus);
    expect(result[0].id).toBe(-(SIMULATED_ID_SEEDS.menus + 1));
    expect(result[1].id).toBe(-(SIMULATED_ID_SEEDS.menus + 2));
  });

  it('preserva los demás campos del row', () => {
    const result = simulateInsertedRows([{ name: 'X', price: 5 }], 0);
    expect(result[0]).toMatchObject({ name: 'X', price: 5 });
  });

  it('devuelve array vacío para input vacío', () => {
    expect(simulateInsertedRows([], 0)).toEqual([]);
  });
});

describe('stripForComparison', () => {
  it('elimina campos meta y FK indicados', () => {
    const row = { id: 1, name: 'Burger', restaurant_id: 5, createdAt: 'x', updatedAt: 'y' };
    expect(stripForComparison(row, ['restaurant_id'])).toEqual({ name: 'Burger' });
  });

  it('sin fkFieldsToStrip elimina solo campos meta', () => {
    const row = { id: 1, name: 'Soda', price: 10, createdAt: 'x' };
    expect(stripForComparison(row, [])).toEqual({ name: 'Soda', price: 10 });
  });
});

describe('compareLevel', () => {
  it('devuelve 100% de similitud para filas idénticas', () => {
    const rows = [{ name: 'A' }, { name: 'B' }];
    const result = compareLevel(rows, rows, []);
    expect(result.similarity).toBe(100);
    expect(result.matchingRecords).toBe(2);
  });

  it('devuelve 0% cuando no hay coincidencias', () => {
    const result = compareLevel([{ name: 'A' }], [{ name: 'Z' }], []);
    expect(result.similarity).toBe(0);
    expect(result.matchingRecords).toBe(0);
  });

  it('devuelve 100% para arrays vacíos', () => {
    expect(compareLevel([], [], []).similarity).toBe(100);
  });

  it('reporta counts correctos', () => {
    const result = compareLevel([{ name: 'A' }, { name: 'B' }], [{ name: 'A' }], []);
    expect(result.originCount).toBe(2);
    expect(result.destCount).toBe(1);
  });
});

describe('buildResponse', () => {
  const maps = {
    menus:         new Map([[1, 10]]),
    items:         new Map([[2, 20]]),
    extraSections: new Map(),
    extraItems:    new Map(),
  };
  const base = {
    dryRun:  false,
    counts:  { menus: 1, items: 1, extraSections: 0, extraItems: 0 },
    idMaps:  maps,
    preview: { menus: [] },
    message: 'Clonado exitoso.',
    from: { duplicateFrom: 'a@x.com', userFrom: { id: 1 }, restaurantFrom: { id: 2 } },
    to:   { duplicateTo:   'b@x.com', userTo:   { id: 3 }, restaurantTo:   { id: 4 } },
  };

  it('incluye success, dryRun, message, clonedCount', () => {
    const res = buildResponse(base);
    expect(res.success).toBe(true);
    expect(res.dryRun).toBe(false);
    expect(res.message).toBe('Clonado exitoso.');
    expect(res.clonedCount).toEqual(base.counts);
  });

  it('incluye idMap como arrays de entradas', () => {
    const res = buildResponse(base);
    expect(res.idMap.menus).toEqual([[1, 10]]);
    expect(res.idMap.items).toEqual([[2, 20]]);
  });

  it('incluye preview solo en dry-run', () => {
    const dryRes  = buildResponse({ ...base, dryRun: true });
    const realRes = buildResponse({ ...base, dryRun: false });
    expect(dryRes.preview).toBeDefined();
    expect(realRes.preview).toBeUndefined();
  });

  it('incluye los metadatos from/to correctos', () => {
    const res = buildResponse(base);
    expect(res.from).toMatchObject({ email: 'a@x.com', user_id: 1, restaurant_id: 2 });
    expect(res.to).toMatchObject({ email: 'b@x.com', user_id: 3, restaurant_id: 4 });
  });
});
