import { BadRequestException } from '@nestjs/common';
import { ViajeDocumentosService } from '../viaje-documentos.service';

/**
 * Vínculos y bajas:
 *  - los cálculos solo ven documentos vigentes (idsVigentes);
 *  - enganchar una factura arrastra sus documentos vinculados;
 *  - la pestaña Documentos marca los anulados y se pueden quitar de un clic;
 *  - anular el viaje libera todo y guarda la lista de lo que tenía.
 */
describe('ViajeDocumentosService: vínculos y anulados', () => {
  const viaje = { id: 'v-1', empresa_id: 'emp-1', estado: 'EN_RUTA', documentos: [] as any[] };

  const crear = (o: any = {}) => {
    const docs: Record<string, any> = o.docs ?? {};
    const asignados: Record<string, any> = o.asignados ?? {};
    const porId = (tabla: string) => ({
      findFirst: jest.fn().mockImplementation((a: any) => Promise.resolve(docs[`${tabla}:${a.where.id}`] ?? null)),
      findMany: jest.fn().mockImplementation((a: any) => {
        if (tabla === 'factura' && o.vinculos && a.select?.nota_remision_factura) return Promise.resolve(o.vinculos);
        const ids: string[] = a.where?.id?.in ?? [];
        return Promise.resolve(ids.map((id) => docs[`${tabla}:${id}`]).filter(Boolean));
      }),
    });
    const prisma: any = {
      usuario: { findUnique: jest.fn().mockResolvedValue({ is_superadmin: true }) },
      empresas: { findUnique: jest.fn().mockResolvedValue({ type: 'subsidiary' }) },
      rubros: { findFirst: jest.fn().mockResolvedValue(null) },
      asignacion_sucursal_caja_usuario: { findMany: jest.fn().mockResolvedValue([]) },
      empresas_sucursales: { findMany: jest.fn().mockResolvedValue([]) },
      depositos: { findMany: jest.fn().mockResolvedValue([]) },
      viaje_documentos: {
        findMany: jest.fn().mockResolvedValue(o.filas ?? []),
        findFirst: jest.fn().mockImplementation((a: any) => Promise.resolve(asignados[a.where.documento_id] ?? null)),
        createMany: jest.fn().mockResolvedValue({ count: 1 }),
        deleteMany: jest.fn().mockResolvedValue({ count: 1 }),
        delete: jest.fn().mockResolvedValue({}),
      },
      viaje_rendiciones: { findMany: jest.fn().mockResolvedValue(o.rendiciones ?? []) },
      factura_cab: porId('factura'),
      nota_credito_cab: porId('nc'),
      nota_remision_cab: porId('remision'),
      recibos_cobro: porId('recibo'),
      pedidos: porId('pedido'),
      presupuesto_cab: porId('presupuesto'),
    };
    const v = { ...viaje, ...(o.viaje ?? {}) };
    const viajes: any = {
      exigirEditable: jest.fn().mockResolvedValue(v),
      findOne: jest.fn().mockResolvedValue(v),
      anular: jest.fn().mockResolvedValue({ ...v, estado: 'ANULADO' }),
    };
    const service = Object.create(ViajeDocumentosService.prototype) as any;
    service.prisma = prisma;
    service.viajes = viajes;
    return { service, prisma, viajes };
  };

  it('idsVigentes deja afuera la factura anulada, la OV cancelada y el documento que ya no existe', async () => {
    const { service, prisma } = crear({
      docs: {
        'factura:f-1': { id: 'f-1', estado: 'Aprobado' },
        'factura:f-2': { id: 'f-2', estado: 'Anulado' },
        'pedido:p-1': { id: 'p-1', estado: 'borrador', estado_orden: 'cancelada' },
        'pedido:p-2': { id: 'p-2', estado: 'borrador', estado_orden: 'aprobada' },
      },
    });
    prisma.viaje_documentos.findMany.mockImplementation((a: any) =>
      Promise.resolve(
        (a.where.tipo === 'FACTURA' ? ['f-1', 'f-2', 'f-borrada'] : ['p-1', 'p-2']).map((d) => ({ documento_id: d })),
      ),
    );

    expect(await service.idsVigentes('v-1', 'FACTURA')).toEqual(['f-1']);
    expect(await service.idsVigentes('v-1', 'PEDIDO')).toEqual(['p-2']);
  });

  it('enganchar una factura arrastra su presupuesto, su OV, sus remisiones y sus NC', async () => {
    const { service, prisma } = crear({
      docs: {
        'factura:f-1': { id: 'f-1', estado: 'Aprobado' },
        'presupuesto:pr-1': { id: 'pr-1', estado: 'CONVERTED' },
        'pedido:pe-1': { id: 'pe-1', estado: 'borrador', estado_orden: 'facturada' },
        'remision:re-1': { id: 're-1', estado: 'Aprobado' },
        'remision:re-2': { id: 're-2', estado: 'Aprobado' },
        'nc:nc-1': { id: 'nc-1', estado: 'Aprobado' },
        'nc:nc-2': { id: 'nc-2', estado: 'Anulado' },
      },
      vinculos: [{
        id: 'f-1', presupuesto_id: 'pr-1', pedido_id: 'pe-1',
        nota_remision_factura: [{ nota_remision_cab_id: 're-1' }, { nota_remision_cab_id: 're-2' }],
        nota_credito_cab: [{ id: 'nc-1' }, { id: 'nc-2' }],
      }],
      // re-2 ya está en otro viaje: no se mueve, se informa.
      asignados: { 're-2': { id: 'x', viaje_id: 'v-2' } },
    });

    const r = await service.agregar('v-1', [{ tipo: 'FACTURA', documento_id: 'f-1', agregado_auto: true }], 'emp-1', 'usr-1');

    const creados = prisma.viaje_documentos.createMany.mock.calls[0][0].data.map((d: any) => `${d.tipo}:${d.documento_id}`);
    expect(creados.sort()).toEqual(['FACTURA:f-1', 'NOTA_CREDITO:nc-1', 'PEDIDO:pe-1', 'PRESUPUESTO:pr-1', 'REMISION:re-1'].sort());
    expect(r.arrastrados).toBe(4);
    expect(r.omitidos).toEqual([expect.objectContaining({ tipo: 'REMISION', documento_id: 're-2', motivo: expect.stringMatching(/otro viaje/) })]);
  });

  it('si el vinculado ya viene en el mismo lote, no lo duplica', async () => {
    const { service, prisma } = crear({
      docs: { 'factura:f-1': { id: 'f-1', estado: 'Aprobado' }, 'nc:nc-1': { id: 'nc-1', estado: 'Aprobado' } },
      vinculos: [{ id: 'f-1', presupuesto_id: null, pedido_id: null, nota_remision_factura: [], nota_credito_cab: [{ id: 'nc-1' }] }],
    });

    const r = await service.agregar('v-1', [
      { tipo: 'FACTURA', documento_id: 'f-1' }, { tipo: 'NOTA_CREDITO', documento_id: 'nc-1' },
    ], 'emp-1', 'usr-1');

    expect(prisma.viaje_documentos.createMany.mock.calls[0][0].data).toHaveLength(2);
    expect(r.arrastrados).toBe(0);
  });

  it('enganchar a mano una OV cancelada se rechaza', async () => {
    const { service } = crear({ docs: { 'pedido:pe-1': { id: 'pe-1', estado: 'borrador', estado_orden: 'cancelada' } } });
    await expect(service.agregar('v-1', [{ tipo: 'PEDIDO', documento_id: 'pe-1' }], 'emp-1', 'usr-1')).rejects.toThrow(BadRequestException);
  });

  it('detallar marca los anulados para la pestaña Documentos', async () => {
    const { service } = crear({
      docs: {
        'factura:f-1': { id: 'f-1', estado: 'Aprobado', dest: '001', dpunexp: '001', dnumdoc: '1', factura_subtotales: [] },
        'factura:f-2': { id: 'f-2', estado: 'Anulado', dest: '001', dpunexp: '001', dnumdoc: '2', factura_subtotales: [] },
      },
    });

    const r = await service.detallar([
      { id: 'fila-1', tipo: 'FACTURA', documento_id: 'f-1' },
      { id: 'fila-2', tipo: 'FACTURA', documento_id: 'f-2' },
    ]);

    expect(r.map((d: any) => [d.id, d.estado, d.anulado])).toEqual([['fila-1', 'Aprobado', false], ['fila-2', 'Anulado', true]]);
  });

  it('quitarAnulados saca solo los dados de baja', async () => {
    const { service, prisma } = crear({
      filas: [{ id: 'fila-1', tipo: 'FACTURA', documento_id: 'f-1' }, { id: 'fila-2', tipo: 'RECIBO', documento_id: 'r-1' }],
      docs: { 'factura:f-1': { id: 'f-1', estado: 'Aprobado' }, 'recibo:r-1': { id: 'r-1', estado: 'anulado' } },
    });

    const r = await service.quitarAnulados('v-1', 'emp-1');

    expect(prisma.viaje_documentos.deleteMany).toHaveBeenCalledWith({ where: { id: { in: ['fila-2'] }, viaje_id: 'v-1' } });
    expect(r).toEqual({ id: 'v-1', quitados: 1 });
  });

  it('quitarAnulados en un viaje cerrado se rechaza', async () => {
    const { service } = crear({ viaje: { estado: 'CERRADO' } });
    await expect(service.quitarAnulados('v-1', 'emp-1')).rejects.toThrow(BadRequestException);
  });

  it('anular guarda la lista de documentos y rendiciones y los libera', async () => {
    const { service, viajes } = crear({
      viaje: { documentos: [{ id: 'fila-1', tipo: 'FACTURA', documento_id: 'f-1' }] },
      docs: { 'factura:f-1': { id: 'f-1', estado: 'Aprobado', dest: '001', dpunexp: '001', dnumdoc: '0000009', factura_subtotales: [{ dtotgralope: 500 }] } },
      rendiciones: [{ rendicion: { numero_rendicion: 'RVI-1', empleado: { nombres: 'Ever', apellidos: 'B' }, monto_rendido_total: 900 } }],
    });

    await service.anularViaje('v-1', 'No se hizo', 'emp-1', 'usr-1');

    const [, motivo, , , registro] = viajes.anular.mock.calls[0];
    expect(motivo).toBe('No se hizo');
    expect(registro.documentos).toEqual([expect.objectContaining({ tipo: 'FACTURA', numero: '001-001-0000009', total: 500 })]);
    expect(registro.rendiciones).toEqual([expect.objectContaining({ numero: 'RVI-1', empleado: 'Ever B' })]);
  });
});
