import { GastosService } from 'src/gastos/gastos.service';
import { OrdenesCompraService } from 'src/ordenes-compra/ordenes-compra.service';
import { PagosProveedorService } from 'src/pagos-proveedor/pagos-proveedor.service';
import { RecepcionesCompraService } from 'src/recepciones-compra/recepciones-compra.service';
import { RequisicionesService } from 'src/requisiciones/requisiciones.service';

describe('Compras/Gastos - flujo integrado operativo', () => {
  it('recorre requisición -> OC -> recepción -> gasto -> CxP -> OP -> pago -> anulación', async () => {
    const empresaId = 'emp-1';
    const userId = 'u-1';
    const proveedorId = 'prov-1';
    const sucursalId = 'suc-1';
    const monedaId = 'mon-1';

    const ordenesCompraService = {
      create: jest.fn().mockResolvedValue({ id: 'oc-1', estado: 'pendiente' }),
      recalculateOrdenCompraEstado: jest.fn().mockResolvedValue(undefined),
    } as unknown as OrdenesCompraService;
    const gastosService = {
      create: jest.fn().mockResolvedValue({ id: 'gasto-1', estado: 'registrado' }),
    } as unknown as GastosService;

    const prismaRequisiciones = {
      config_compras: {
        findUnique: jest.fn().mockResolvedValue({
          empresa_id: empresaId,
          habilitar_requisicion_compra: true,
        }),
        create: jest.fn(),
      },
      requisicion_cab: {
        findFirst: jest
          .fn()
          .mockResolvedValueOnce({
            id: 'req-compra-1',
            empresa_id: empresaId,
            activo: true,
            origen_tipo: 'compra',
            estado: 'aprobada',
            descripcion: 'Requisición compra integrada',
            requisicion_det: [
              {
                id: 'req-compra-det-1',
                producto_id: 'prod-1',
                tipo_gasto_id: null,
                descripcion: 'Item compra',
                cantidad_solicitada: 5,
                cantidad_ejecutada: 0,
                precio_unitario: 200,
                porcentaje_iva: 10,
              },
            ],
          })
          .mockResolvedValueOnce({
            id: 'req-gasto-1',
            empresa_id: empresaId,
            activo: true,
            origen_tipo: 'gasto',
            estado: 'aprobada',
            descripcion: 'Requisición gasto integrada',
            requisicion_det: [
              {
                id: 'req-gasto-det-1',
                producto_id: null,
                tipo_gasto_id: 'tipo-gasto-1',
                descripcion: 'Servicio técnico',
                cantidad_solicitada: 1,
                cantidad_ejecutada: 0,
                precio_unitario: 500,
                porcentaje_iva: 10,
              },
            ],
          }),
      },
    };

    const requisicionesService = new RequisicionesService(
      prismaRequisiciones as any,
      ordenesCompraService as any,
      gastosService as any,
    );

    await requisicionesService.crearOrdenCompraDesdeRequisicion(
      'req-compra-1',
      empresaId,
      {
        sucursal_id: sucursalId,
        proveedor_id: proveedorId,
        moneda_id: monedaId,
        fecha_emision: '2026-04-27',
      } as any,
      userId,
    );
    expect((ordenesCompraService as any).create).toHaveBeenCalledWith(
      expect.objectContaining({
        cabecera: expect.objectContaining({
          requisicion_id: 'req-compra-1',
          proveedor_id: proveedorId,
        }),
      }),
      empresaId,
      userId,
    );

    const txRecepciones = {
      config_compras: {
        findUnique: jest.fn().mockResolvedValue({
          empresa_id: empresaId,
          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: empresaId,
          sucursal_id: sucursalId,
          proveedor_id: proveedorId,
          estado: 'pendiente',
          orden_compra_det: [
            {
              id: 'oc-det-1',
              producto_id: 'prod-1',
              cantidad: 5,
              cantidad_recibida: 0,
            },
          ],
        }),
      },
      recepcion_compra_cab: {
        create: jest.fn().mockResolvedValue({
          id: 'rec-1',
          deposito_id: null,
        }),
      },
      recepcion_compra_det: {
        create: jest.fn().mockResolvedValue(undefined),
      },
      orden_compra_det: {
        update: jest.fn().mockResolvedValue(undefined),
      },
    };
    const prismaRecepciones = {
      $transaction: jest.fn(async (cb: any) => cb(txRecepciones)),
    };
    const recepcionesService = new RecepcionesCompraService(
      prismaRecepciones as any,
      ordenesCompraService as any,
    );
    jest.spyOn(recepcionesService, 'findOne').mockResolvedValue({ id: 'rec-1' } as any);

    await recepcionesService.create(
      {
        cabecera: { orden_compra_id: 'oc-1', fecha_recepcion: '2026-04-27' },
        detalles: [{ orden_compra_det_id: 'oc-det-1', cantidad_recibida: 5 }],
      } as any,
      empresaId,
      userId,
    );
    expect((ordenesCompraService as any).recalculateOrdenCompraEstado).toHaveBeenCalled();

    await requisicionesService.crearGastoDesdeRequisicion(
      'req-gasto-1',
      empresaId,
      {
        proveedor_id: proveedorId,
        sucursal_id: sucursalId,
        moneda_id: monedaId,
        forma_pago: 'CREDITO',
        fecha_emision: '2026-04-27',
      } as any,
      userId,
    );
    expect((gastosService as any).create).toHaveBeenCalledWith(
      expect.objectContaining({
        cabecera: expect.objectContaining({
          requisicion_id: 'req-gasto-1',
          proveedor_id: proveedorId,
        }),
      }),
      empresaId,
      userId,
    );

    const opState = {
      cabecera: null as any,
      detalles: [] as any[],
      cuentas: new Map<string, any>([
        [
          'cxp-compra-1',
          {
            id: 'cxp-compra-1',
            empresa_id: empresaId,
            proveedor_id: proveedorId,
            compra_id: 'compra-1',
            origen_tipo: 'compra',
            origen_id: 'compra-1',
            moneda_id: monedaId,
            monto_original: 1000,
            monto_pagado: 0,
            saldo_pendiente: 1000,
            estado: 'pendiente',
            activo: true,
          },
        ],
        [
          'cxp-gasto-1',
          {
            id: 'cxp-gasto-1',
            empresa_id: empresaId,
            proveedor_id: proveedorId,
            compra_id: null,
            origen_tipo: 'gasto',
            origen_id: 'gasto-1',
            moneda_id: monedaId,
            monto_original: 500,
            monto_pagado: 0,
            saldo_pendiente: 500,
            estado: 'pendiente',
            activo: true,
          },
        ],
      ]),
      pagos: [] as any[],
    };

    const txPagos = {
      config_compras: {
        findUnique: jest.fn().mockResolvedValue({
          empresa_id: empresaId,
          habilitar_orden_pago: true,
          aplicar_pago_al_guardar: false,
        }),
        create: jest.fn(),
      },
      proveedores: {
        findFirst: jest.fn().mockResolvedValue({ id: proveedorId }),
      },
      empresas_sucursales: {
        findFirst: jest.fn().mockResolvedValue({ id: sucursalId, config: {} }),
      },
      cuentas_pagar: {
        findMany: jest.fn(async (args: any) => {
          const ids = args?.where?.id?.in || [];
          return ids
            .map((id: string) => opState.cuentas.get(id))
            .filter((item: any) => Boolean(item))
            .map((item: any) => ({ ...item }));
        }),
        findUnique: jest.fn(async (args: any) => {
          const id = args?.where?.id;
          const item = opState.cuentas.get(id);
          return item ? { ...item } : null;
        }),
        update: jest.fn(async (args: any) => {
          const id = args?.where?.id;
          const prev = opState.cuentas.get(id);
          if (!prev) return null;
          const data = args?.data || {};
          const next = { ...prev };
          if (data.monto_pagado?.increment !== undefined) {
            next.monto_pagado += Number(data.monto_pagado.increment);
          } else if (data.monto_pagado !== undefined) {
            next.monto_pagado = Number(data.monto_pagado);
          }
          if (data.saldo_pendiente !== undefined) next.saldo_pendiente = Number(data.saldo_pendiente);
          if (data.estado !== undefined) next.estado = data.estado;
          opState.cuentas.set(id, next);
          return { ...next };
        }),
      },
      orden_pago_proveedor_cab: {
        create: jest.fn(async (args: any) => {
          opState.cabecera = {
            id: 'op-1',
            ...args.data,
            activo: true,
            anulado: false,
          };
          return { ...opState.cabecera };
        }),
        findFirst: jest.fn(async () => {
          if (!opState.cabecera) return null;
          return {
            ...opState.cabecera,
            orden_pago_proveedor_det: opState.detalles.map((d) => ({ ...d })),
          };
        }),
        update: jest.fn(async (args: any) => {
          if (!opState.cabecera) return null;
          opState.cabecera = { ...opState.cabecera, ...args.data };
          return { ...opState.cabecera };
        }),
      },
      orden_pago_proveedor_det: {
        create: jest.fn(async (args: any) => {
          opState.detalles.push({ ...args.data });
          return { ...args.data };
        }),
      },
      pagos_proveedor: {
        create: jest.fn(async (args: any) => {
          opState.pagos.push({
            id: `pago-${opState.pagos.length + 1}`,
            ...args.data,
          });
          return opState.pagos[opState.pagos.length - 1];
        }),
        updateMany: jest.fn(async (args: any) => {
          let count = 0;
          opState.pagos = opState.pagos.map((pago) => {
            if (
              pago.cuenta_pagar_id === args.where.cuenta_pagar_id &&
              pago.numero_recibo === args.where.numero_recibo &&
              pago.anulado === false
            ) {
              count += 1;
              return { ...pago, ...args.data };
            }
            return pago;
          });
          return { count };
        }),
      },
      numeraciones_documento: {
        update: jest.fn().mockResolvedValue(undefined),
      },
    };

    const prismaPagos = {
      $transaction: jest.fn(async (cb: any) => cb(txPagos)),
    };
    const contabilidadIntegracion = {
      integrarOrdenPago: jest.fn().mockResolvedValue(undefined),
      revertirDocumento: jest.fn().mockResolvedValue(undefined),
    };
    const pagosProveedorService = new PagosProveedorService(
      prismaPagos as any,
      contabilidadIntegracion as any,
    );

    jest.spyOn(pagosProveedorService as any, 'validateSesionCaja').mockResolvedValue(undefined);
    jest.spyOn(pagosProveedorService as any, 'getNextNumeroOrdenPago').mockResolvedValue({
      numeroOrden: 'OP-0001',
      numeracionId: 'num-op-1',
    });
    jest.spyOn(pagosProveedorService as any, 'recalculateCompraEstado').mockResolvedValue(undefined);
    jest.spyOn(pagosProveedorService as any, 'recalculateAutoOrderEstado').mockResolvedValue(undefined);
    jest.spyOn(pagosProveedorService as any, 'recalculateGastoEstado').mockResolvedValue(undefined);
    jest.spyOn(pagosProveedorService, 'findOne').mockImplementation(async (id: string) => ({
      id,
      estado: opState.cabecera?.estado ?? 'BORRADOR',
    } as any));

    const op = await pagosProveedorService.create(
      {
        proveedor_id: proveedorId,
        sucursal_id: sucursalId,
        moneda_id: monedaId,
        fecha_pago: '2026-04-27',
        detalles: [
          { cuenta_pagar_id: 'cxp-compra-1', monto: 1000, medio_pago_id: null },
          { cuenta_pagar_id: 'cxp-gasto-1', monto: 500, medio_pago_id: null },
        ],
      } as any,
      empresaId,
      userId,
    );

    expect(op.estado).toBe('BORRADOR');
    expect(opState.cuentas.get('cxp-compra-1').saldo_pendiente).toBe(1000);
    expect(opState.cuentas.get('cxp-gasto-1').saldo_pendiente).toBe(500);

    await pagosProveedorService.ejecutar('op-1', empresaId, userId);
    expect(opState.cuentas.get('cxp-compra-1').estado).toBe('pagada');
    expect(opState.cuentas.get('cxp-gasto-1').estado).toBe('pagada');
    expect(opState.pagos).toHaveLength(2);

    await pagosProveedorService.anular('op-1', { motivo: 'Reversa QA' } as any, empresaId, userId);
    expect(opState.cabecera.estado).toBe('ANULADA');
    expect(opState.cuentas.get('cxp-compra-1').estado).toBe('pendiente');
    expect(opState.cuentas.get('cxp-gasto-1').estado).toBe('pendiente');
    expect(contabilidadIntegracion.revertirDocumento).toHaveBeenCalledWith(
      'orden_pago_proveedor_cab',
      'op-1',
      userId,
    );
  });
});
