import { BadRequestException } from '@nestjs/common';
import { RecepcionesCompraService } from './recepciones-compra.service';

describe('RecepcionesCompraService - reglas F3', () => {
  const baseDto = {
    cabecera: {
      orden_compra_id: 'oc-1',
      fecha_recepcion: '2026-04-26',
      observaciones: 'Recepción parcial',
    },
    detalles: [{ orden_compra_det_id: 'det-1', cantidad_recibida: 1 }],
  };

  const createService = (txOverrides: Record<string, any> = {}) => {
    const tx = {
      config_compras: {
        findUnique: jest.fn().mockResolvedValue({
          empresa_id: 'emp-1',
          habilitar_recepcion_compra: true,
          afectar_stock_en_compra_directa: true,
        }),
        create: jest.fn(),
      },
      orden_compra_cab: {
        findFirst: jest.fn().mockResolvedValue({
          id: 'oc-1',
          empresa_id: 'emp-1',
          sucursal_id: 'suc-1',
          proveedor_id: 'prov-1',
          estado: 'pendiente',
          orden_compra_det: [
            {
              id: 'det-1',
              producto_id: 'prod-1',
              cantidad: 10,
              cantidad_recibida: 2,
            },
          ],
        }),
      },
      recepcion_compra_cab: {
        create: jest.fn().mockResolvedValue({
          id: 'rec-1',
          deposito_id: null,
          orden_compra_id: 'oc-1',
        }),
      },
      recepcion_compra_det: {
        create: jest.fn().mockResolvedValue(undefined),
      },
      orden_compra_det: {
        update: jest.fn().mockResolvedValue(undefined),
      },
      ...txOverrides,
    };

    const prisma = {
      $transaction: jest.fn(async (cb: any) => cb(tx)),
    };

    const ordenesCompraService = {
      recalculateOrdenCompraEstado: jest.fn().mockResolvedValue(undefined),
    };
    const auditService = {
      log: jest.fn().mockResolvedValue(undefined),
    };

    const service = new RecepcionesCompraService(prisma as any, ordenesCompraService as any, auditService as any);
    jest.spyOn(service, 'findOne').mockResolvedValue({ id: 'rec-1' } as any);

    return { service, tx, prisma, ordenesCompraService, auditService };
  };

  it('bloquea si la recepción está deshabilitada por configuración', async () => {
    const { service, tx } = createService();
    tx.config_compras.findUnique.mockResolvedValue({
      empresa_id: 'emp-1',
      habilitar_recepcion_compra: false,
      afectar_stock_en_compra_directa: true,
    });

    await expect(service.create(baseDto as any, 'emp-1', 'u-1')).rejects.toThrow(BadRequestException);
  });

  it('bloquea cuando la orden está pendiente de aprobación', async () => {
    const { service, tx } = createService();
    tx.orden_compra_cab.findFirst.mockResolvedValue({
      id: 'oc-1',
      empresa_id: 'emp-1',
      sucursal_id: 'suc-1',
      proveedor_id: 'prov-1',
      estado: 'pendiente_aprobacion',
      orden_compra_det: [{ id: 'det-1', producto_id: 'prod-1', cantidad: 10, cantidad_recibida: 0 }],
    });

    await expect(service.create(baseDto as any, 'emp-1', 'u-1')).rejects.toThrow(BadRequestException);
  });

  it('bloquea si la recepción excede cantidad ordenada pendiente', async () => {
    const { service, tx } = createService();
    tx.orden_compra_cab.findFirst.mockResolvedValue({
      id: 'oc-1',
      empresa_id: 'emp-1',
      sucursal_id: 'suc-1',
      proveedor_id: 'prov-1',
      estado: 'pendiente',
      orden_compra_det: [{ id: 'det-1', producto_id: 'prod-1', cantidad: 5, cantidad_recibida: 2 }],
    });

    const dto = {
      ...baseDto,
      detalles: [{ orden_compra_det_id: 'det-1', cantidad_recibida: 4 }],
    };

    await expect(service.create(dto as any, 'emp-1', 'u-1')).rejects.toThrow(BadRequestException);
  });

  it('actualiza recepción y cantidades por detalle cuando payload es válido', async () => {
    const { service, tx, ordenesCompraService, auditService } = createService();

    const dto = {
      ...baseDto,
      detalles: [
        { orden_compra_det_id: 'det-1', cantidad_recibida: 1.5 },
        { orden_compra_det_id: 'det-1', cantidad_recibida: 0.5 },
      ],
    };

    const result = await service.create(dto as any, 'emp-1', 'u-1');

    expect(tx.recepcion_compra_cab.create).toHaveBeenCalledTimes(1);
    expect(tx.recepcion_compra_det.create).toHaveBeenCalledTimes(2);
    expect(tx.orden_compra_det.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: 'det-1' },
        data: expect.objectContaining({
          cantidad_recibida: { increment: 2 },
        }),
      }),
    );
    expect(ordenesCompraService.recalculateOrdenCompraEstado).toHaveBeenCalledWith(expect.anything(), 'oc-1');
    expect(auditService.log).toHaveBeenCalledWith(
      expect.objectContaining({
        action: 'CREATE',
        entity_type: 'recepcion_compra',
        entity_id: 'rec-1',
      }),
    );
    expect(auditService.log).toHaveBeenCalledWith(
      expect.objectContaining({
        action: 'RECEPCION_CREATE',
        entity_type: 'orden_compra',
        entity_id: 'oc-1',
      }),
    );
    expect(result).toEqual({ id: 'rec-1' });
  });

  it('registra recepción libre con proveedor, depósito y productos cuando OC está deshabilitada', async () => {
    const { service, tx } = createService({
      proveedores: {
        findFirst: jest.fn().mockResolvedValue({ id: 'prov-1' }),
      },
      depositos: {
        findFirst: jest.fn().mockResolvedValue({ id: 'dep-1', sucursal_id: 'suc-1' }),
      },
      productos: {
        findMany: jest.fn().mockResolvedValue([{ id: 'prod-1' }]),
      },
      recepcion_compra_cab: {
        create: jest.fn().mockResolvedValue({ id: 'rec-1', deposito_id: 'dep-1', orden_compra_id: null }),
      },
      recepcion_compra_det: {
        create: jest.fn(),
        createMany: jest.fn().mockResolvedValue({ count: 1 }),
      },
    });
    tx.config_compras.findUnique.mockResolvedValue({
      empresa_id: 'emp-1',
      habilitar_orden_compra: false,
      habilitar_recepcion_compra: true,
      afectar_stock_en_compra_directa: false,
    });
    const aplicarStock = jest.spyOn(service as any, 'aplicarStockRecepcion').mockResolvedValue(undefined);

    await service.create(
      {
        cabecera: {
          proveedor_id: 'prov-1',
          deposito_id: 'dep-1',
          fecha_recepcion: '2026-07-16',
        },
        detalles: [{ producto_id: 'prod-1', cantidad_recibida: 3 }],
      } as any,
      'emp-1',
      'u-1',
    );

    expect(tx.orden_compra_cab.findFirst).not.toHaveBeenCalled();
    expect(tx.recepcion_compra_cab.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          proveedor_id: 'prov-1',
          deposito_id: 'dep-1',
          orden_compra_id: null,
        }),
      }),
    );
    expect((tx.recepcion_compra_det as any).createMany).toHaveBeenCalledWith(
      expect.objectContaining({
        data: [expect.objectContaining({ orden_compra_det_id: null, producto_id: 'prod-1' })],
      }),
    );
    expect(aplicarStock).toHaveBeenCalledWith(
      expect.anything(),
      expect.objectContaining({
        depositoId: 'dep-1',
        detalles: [{ producto_id: 'prod-1', cantidad_recibida: 3 }],
      }),
    );
  });

  describe('anular', () => {
    const createAnularService = (txOverrides: Record<string, any> = {}) => {
      const tx = {
        config_compras: {
          findUnique: jest.fn().mockResolvedValue({
            empresa_id: 'emp-1',
            habilitar_recepcion_compra: true,
          }),
          create: jest.fn(),
        },
        recepcion_compra_cab: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'rec-1',
            empresa_id: 'emp-1',
            deposito_id: null,
            anulado: false,
            estado: 'aplicada',
            recepcion_compra_det: [
              { id: 'rd-1', orden_compra_det_id: 'det-1', producto_id: 'prod-1', cantidad_recibida: 2 },
            ],
            orden_compra_cab: { id: 'oc-1', estado: 'pendiente' },
          }),
          update: jest.fn().mockResolvedValue(undefined),
        },
        orden_compra_det: {
          findMany: jest.fn().mockResolvedValue([{ id: 'det-1', cantidad_recibida: 4 }]),
          update: jest.fn().mockResolvedValue(undefined),
        },
        compra_det: {
          findMany: jest.fn().mockResolvedValue([{ orden_compra_det_id: 'det-1', cantidad: 1 }]),
        },
        ...txOverrides,
      };

      const prisma = {
        $transaction: jest.fn(async (cb: any) => cb(tx)),
      };

      const ordenesCompraService = {
        recalculateOrdenCompraEstado: jest.fn().mockResolvedValue(undefined),
      };

      const service = new RecepcionesCompraService(prisma as any, ordenesCompraService as any, {} as any);
      jest.spyOn(service, 'findOne').mockResolvedValue({ id: 'rec-1', estado: 'anulada' } as any);

      return { service, tx, ordenesCompraService };
    };

    it('bloquea anulación cuando al revertir recepción quedaría más facturado que recibido', async () => {
      const { service, tx } = createAnularService();
      tx.compra_det.findMany.mockResolvedValue([{ orden_compra_det_id: 'det-1', cantidad: 3 }]);

      await expect(service.anular('rec-1', { motivo: 'Error' } as any, 'emp-1')).rejects.toThrow(BadRequestException);
    });

    it('anula recepción válida y recalcula estado de OC', async () => {
      const { service, tx, ordenesCompraService } = createAnularService();

      const result = await service.anular('rec-1', { motivo: 'Carga duplicada' } as any, 'emp-1');

      expect(tx.orden_compra_det.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'det-1' },
          data: expect.objectContaining({
            cantidad_recibida: { decrement: 2 },
          }),
        }),
      );
      expect(tx.recepcion_compra_cab.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'rec-1' },
          data: expect.objectContaining({
            estado: 'anulada',
            anulado: true,
            activo: false,
          }),
        }),
      );
      expect(ordenesCompraService.recalculateOrdenCompraEstado).toHaveBeenCalledWith(expect.anything(), 'oc-1');
      expect(result).toEqual({ id: 'rec-1', estado: 'anulada' });
    });

    it('anula una recepción libre y revierte stock sin recalcular OC', async () => {
      const { service, tx, ordenesCompraService } = createAnularService();
      tx.recepcion_compra_cab.findFirst.mockResolvedValue({
        id: 'rec-libre',
        empresa_id: 'emp-1',
        deposito_id: 'dep-1',
        anulado: false,
        estado: 'aplicada',
        recepcion_compra_det: [{ id: 'rd-1', orden_compra_det_id: null, producto_id: 'prod-1', cantidad_recibida: 2 }],
        orden_compra_cab: null,
      });
      const revertirStock = jest.spyOn(service as any, 'revertirStockRecepcion').mockResolvedValue(undefined);

      await service.anular('rec-libre', { motivo: 'Error' } as any, 'emp-1', 'u-1');

      expect(revertirStock).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({ recepcionId: 'rec-libre', depositoId: 'dep-1' }),
      );
      expect(tx.orden_compra_det.update).not.toHaveBeenCalled();
      expect(ordenesCompraService.recalculateOrdenCompraEstado).not.toHaveBeenCalled();
    });
  });

  describe('update', () => {
    const createUpdateService = (txOverrides: Record<string, any> = {}) => {
      const tx = {
        config_compras: {
          findUnique: jest.fn().mockResolvedValue({
            empresa_id: 'emp-1',
            habilitar_recepcion_compra: true,
            afectar_stock_en_compra_directa: true,
          }),
          create: jest.fn(),
        },
        recepcion_compra_cab: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'rec-1',
            empresa_id: 'emp-1',
            orden_compra_id: 'oc-1',
            estado: 'aplicada',
            anulado: false,
            deposito_id: null,
            usuario_id: 'u-1',
            fecha_recepcion: new Date('2026-05-02T00:00:00.000Z'),
            recepcion_compra_det: [{ orden_compra_det_id: 'det-1', producto_id: 'prod-1', cantidad_recibida: 2 }],
          }),
          update: jest.fn().mockResolvedValue(undefined),
        },
        orden_compra_cab: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'oc-1',
            estado: 'pendiente',
            orden_compra_det: [{ id: 'det-1', producto_id: 'prod-1', cantidad: 10, cantidad_recibida: 5 }],
          }),
        },
        compra_det: {
          findMany: jest.fn().mockResolvedValue([{ orden_compra_det_id: 'det-1', cantidad: 3 }]),
        },
        orden_compra_det: {
          update: jest.fn().mockResolvedValue(undefined),
        },
        recepcion_compra_det: {
          deleteMany: jest.fn().mockResolvedValue(undefined),
          createMany: jest.fn().mockResolvedValue(undefined),
        },
        ...txOverrides,
      };

      const prisma = {
        $transaction: jest.fn(async (cb: any) => cb(tx)),
      };

      const ordenesCompraService = {
        recalculateOrdenCompraEstado: jest.fn().mockResolvedValue(undefined),
      };

      const service = new RecepcionesCompraService(prisma as any, ordenesCompraService as any, {} as any);
      jest.spyOn(service, 'findOne').mockResolvedValue({ id: 'rec-1', estado: 'aplicada' } as any);

      return { service, tx, ordenesCompraService };
    };

    it('edita recepción aplicada ajustando cantidades sin violar facturación', async () => {
      const { service, tx, ordenesCompraService } = createUpdateService();
      const dto = {
        cabecera: {
          orden_compra_id: 'oc-1',
          fecha_recepcion: '2026-05-03',
          observaciones: 'Ajuste por recuento',
        },
        detalles: [{ orden_compra_det_id: 'det-1', cantidad_recibida: 1 }],
      };

      const result = await service.update('rec-1', dto as any, 'emp-1', 'u-2');

      expect(tx.orden_compra_det.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'det-1' },
          data: expect.objectContaining({ cantidad_recibida: 4 }),
        }),
      );
      expect(tx.recepcion_compra_det.deleteMany).toHaveBeenCalledWith({ where: { recepcion_id: 'rec-1' } });
      expect(tx.recepcion_compra_det.createMany).toHaveBeenCalledTimes(1);
      expect(tx.recepcion_compra_cab.update).toHaveBeenCalledTimes(1);
      expect(ordenesCompraService.recalculateOrdenCompraEstado).toHaveBeenCalledWith(expect.anything(), 'oc-1');
      expect(result).toEqual({ id: 'rec-1', estado: 'aplicada' });
    });

    it('bloquea edición cuando la nueva recepción dejaría más facturado que recibido', async () => {
      const { service, tx } = createUpdateService();
      tx.compra_det.findMany.mockResolvedValue([{ orden_compra_det_id: 'det-1', cantidad: 5 }]);

      const dto = {
        cabecera: {
          orden_compra_id: 'oc-1',
          fecha_recepcion: '2026-05-03',
        },
        detalles: [{ orden_compra_det_id: 'det-1', cantidad_recibida: 1 }],
      };

      await expect(service.update('rec-1', dto as any, 'emp-1', 'u-2')).rejects.toThrow(BadRequestException);
    });
  });
});
