import { BadRequestException } from '@nestjs/common';
import { ImportacionesCosteoService } from './importaciones-costeo.service';

const buildItems = (rows: Array<Partial<{ id: string; cantidad: number; fob_total_usd: number; peso_unit_kg: number; cbm_unit: number }>>) =>
  rows.map((r) => ({ id: r.id, cantidad: r.cantidad ?? 1, fob_total_usd: r.fob_total_usd ?? 0, peso_unit_kg: r.peso_unit_kg ?? 0, cbm_unit: r.cbm_unit ?? 0 }));

const buildTx = (items: any[], componentes: any[]) => ({
  imp_embarque_items: {
    findMany: jest.fn().mockResolvedValue(items),
    update: jest.fn(),
  },
  imp_componentes_costo: {
    findMany: jest.fn().mockResolvedValue(componentes),
  },
  imp_embarques: {
    update: jest.fn(),
  },
});

describe('ImportacionesCosteoService', () => {
  let service: ImportacionesCosteoService;

  beforeEach(() => {
    service = new ImportacionesCosteoService();
  });

  it('prorratea costos compartidos por FOB y suma costos directos', async () => {
    const tx: any = buildTx(
      buildItems([
        { id: 'item-a', fob_total_usd: 100, cantidad: 1 },
        { id: 'item-b', fob_total_usd: 300, cantidad: 3 },
      ]),
      [
        { id: 'direct-a', es_directo: true, item_id: 'item-a', importe_gs: 50 },
        { id: 'shared', es_directo: false, item_id: null, importe_gs: 400, criterio_prorrateo: 'FOB' },
      ],
    );

    await service.recalcular(tx, 'empresa-1', 'embarque-1');

    expect(tx.imp_embarque_items.update).toHaveBeenNthCalledWith(1, {
      where: { id: 'item-a' },
      data: expect.objectContaining({
        costo_directo_gs: 50,
        costo_compartido_gs: 100,
        costo_total_gs: 150,
        costo_unitario_gs: 150,
      }),
    });
    expect(tx.imp_embarque_items.update).toHaveBeenNthCalledWith(2, {
      where: { id: 'item-b' },
      data: expect.objectContaining({
        costo_directo_gs: 0,
        costo_compartido_gs: 300,
        costo_total_gs: 300,
        costo_unitario_gs: 100,
      }),
    });
    expect(tx.imp_embarques.update).toHaveBeenCalledWith({
      where: { id: 'embarque-1' },
      data: expect.objectContaining({
        fob_total_usd: 400,
        costo_total_gs: 450,
      }),
    });
  });

  it('reparte por partes IGUALES sin importar el FOB', async () => {
    const tx: any = buildTx(
      buildItems([
        { id: 'a', cantidad: 1, fob_total_usd: 0 },
        { id: 'b', cantidad: 2, fob_total_usd: 0 },
        { id: 'c', cantidad: 1, fob_total_usd: 0 },
      ]),
      [{ id: 'shared', es_directo: false, importe_gs: 300, criterio_prorrateo: 'IGUAL' }],
    );

    await service.recalcular(tx, 'empresa-1', 'embarque-1');

    const calls = tx.imp_embarque_items.update.mock.calls.map(([arg]: any[]) => arg);
    expect(calls[0].data.costo_compartido_gs).toBe(100);
    expect(calls[1].data.costo_compartido_gs).toBe(100);
    expect(calls[2].data.costo_compartido_gs).toBe(100);
  });

  it('prorratea por PESO usando peso_unit_kg * cantidad', async () => {
    const tx: any = buildTx(
      buildItems([
        { id: 'a', cantidad: 2, peso_unit_kg: 10 }, // 20 kg
        { id: 'b', cantidad: 5, peso_unit_kg: 4 }, // 20 kg
      ]),
      [{ id: 'shared', es_directo: false, importe_gs: 400, criterio_prorrateo: 'PESO' }],
    );

    await service.recalcular(tx, 'empresa-1', 'embarque-1');

    const calls = tx.imp_embarque_items.update.mock.calls.map(([arg]: any[]) => arg);
    expect(calls[0].data.costo_compartido_gs).toBe(200);
    expect(calls[1].data.costo_compartido_gs).toBe(200);
  });

  it('prorratea por CBM usando cbm_unit * cantidad', async () => {
    const tx: any = buildTx(
      buildItems([
        { id: 'a', cantidad: 1, cbm_unit: 1 },
        { id: 'b', cantidad: 1, cbm_unit: 3 },
      ]),
      [{ id: 'shared', es_directo: false, importe_gs: 800, criterio_prorrateo: 'CBM' }],
    );

    await service.recalcular(tx, 'empresa-1', 'embarque-1');

    const calls = tx.imp_embarque_items.update.mock.calls.map(([arg]: any[]) => arg);
    expect(calls[0].data.costo_compartido_gs).toBe(200);
    expect(calls[1].data.costo_compartido_gs).toBe(600);
  });

  it('prorratea por CANTIDAD usando cantidad pura', async () => {
    const tx: any = buildTx(
      buildItems([
        { id: 'a', cantidad: 1 },
        { id: 'b', cantidad: 4 },
      ]),
      [{ id: 'shared', es_directo: false, importe_gs: 500, criterio_prorrateo: 'CANTIDAD' }],
    );

    await service.recalcular(tx, 'empresa-1', 'embarque-1');

    const calls = tx.imp_embarque_items.update.mock.calls.map(([arg]: any[]) => arg);
    expect(calls[0].data.costo_compartido_gs).toBe(100);
    expect(calls[1].data.costo_compartido_gs).toBe(400);
  });

  it('lanza error si la base agregada para el criterio elegido es cero', async () => {
    const tx: any = buildTx(
      buildItems([{ id: 'a', cantidad: 1, peso_unit_kg: 0 }]),
      [{ id: 'shared', es_directo: false, importe_gs: 100, criterio_prorrateo: 'PESO' }],
    );

    await expect(service.recalcular(tx, 'empresa-1', 'embarque-1')).rejects.toBeInstanceOf(BadRequestException);
  });

  it('rechaza criterios fuera de la lista permitida', async () => {
    const tx: any = buildTx(
      buildItems([{ id: 'a', cantidad: 1, fob_total_usd: 1 }]),
      [{ id: 'shared', es_directo: false, importe_gs: 100, criterio_prorrateo: 'INVENTADO' }],
    );

    await expect(service.recalcular(tx, 'empresa-1', 'embarque-1')).rejects.toBeInstanceOf(BadRequestException);
  });
});
