import { ContabilidadIntegracionService } from '../integracion.service';
import { FaltaMapeoError } from '../falta-mapeo.error';
import { MapeoCuentasService } from '../mapeo-cuentas.service';
import { ReglaMapeo } from '../reglas-mapeo';

const cuentaDe = (c: string) => `cta-${c}`;

/**
 * Agrega a un mock de `mapeo` un `crearResolvedor` de producción: corre
 * `MapeoCuentasService.prototype.crearResolvedor` y `resolverConcepto` reales sobre
 * un Prisma de mentira:
 * - `cont_mapeo_reglas.findMany` devuelve `reglas`;
 * - `cont_plan_cuentas.findFirst` da toda cuenta de regla por válida, salvo las
 *   que empiezan con `grupo-` (de agrupación: la regla no se puede usar);
 * - `cont_mapeo_cuentas.findUnique` (el mapeo por defecto) pregunta al
 *   `resolverConcepto` / `getCuentaPorConcepto` del mock, leídos en cada llamada,
 *   así los tests que los reemplazan (faltas, errores de base) siguen mandando.
 * `prisma` aporta `categorias` para `prepararCategorias`.
 */
export const conResolvedor = (mapeo: any, reglas: ReglaMapeo[] = [], prisma: any = {}) => {
  const servicio = Object.create(MapeoCuentasService.prototype) as any;
  servicio.prisma = {
    cont_mapeo_reglas: { findMany: jest.fn().mockImplementation(async () => reglas) },
    cont_plan_cuentas: {
      findFirst: jest.fn().mockImplementation(async ({ where }: any) => ({ codigo: where.id, active: true, acepta_movimientos: !String(where.id).startsWith('grupo-') })),
    },
    cont_mapeo_cuentas: {
      findUnique: jest.fn().mockImplementation(async ({ where }: any) => {
        const { empresa_id: e, concepto: c } = where.empresa_id_concepto;
        const r = mapeo.resolverConcepto ? await mapeo.resolverConcepto(e, c) : null;
        if (r?.modo === 'SUMAR_A_PRINCIPAL') return { cuenta_id: null, modo: 'SUMAR_A_PRINCIPAL', cuenta: null };
        const cuentaId = await mapeo.getCuentaPorConcepto(e, c);
        return { cuenta_id: cuentaId, modo: 'CUENTA', cuenta: { codigo: cuentaId, active: true, acepta_movimientos: true } };
      }),
    },
    get categorias() {
      return prisma.categorias ?? { findMany: async () => [] };
    },
  };
  mapeo.crearResolvedor = jest.fn().mockImplementation((e: string, contexto: any) => servicio.crearResolvedor(e, contexto));
  return mapeo;
};

/**
 * Integrador armado a mano para tests (sin Nest ni base). Cada concepto resuelve
 * a `cta-<CONCEPTO>`, salvo:
 * - los de `faltan`, que rechazan con FaltaMapeoError (no están mapeados);
 * - los de `modos`, que `resolverConcepto` devuelve como SUMAR_A_PRINCIPAL;
 * - los que cubre una de `reglas` (con las dimensiones del documento).
 * `prisma` se mezcla sobre un Prisma mínimo (documentos, transacción y las
 * fichas de donde salen las dimensiones de cabecera, vacías).
 */
export const crearIntegracionMock = (
  prisma: any,
  faltan: string[] = [],
  modos: Record<string, 'SUMAR_A_PRINCIPAL'> = {},
  reglas: ReglaMapeo[] = [],
) => {
  const s = Object.create(ContabilidadIntegracionService.prototype) as any;
  s.logger = { log: jest.fn(), warn: jest.fn(), error: jest.fn() };
  s.prisma = {
    cont_documentos: {
      findFirst: jest.fn().mockResolvedValue(null),
      upsert: jest.fn().mockResolvedValue({ id: 'd' }),
      create: jest.fn().mockResolvedValue({ id: 'd' }),
    },
    $transaction: jest.fn().mockImplementation((fn: any) => fn(s.prisma)),
    imp_componentes_costo: { findFirst: jest.fn().mockResolvedValue(null) },
    empresas_sucursales: { findFirst: jest.fn().mockResolvedValue(null) },
    clientes: { findUnique: jest.fn().mockResolvedValue(null) },
    proveedores: { findFirst: jest.fn().mockResolvedValue(null) },
    moneda: { findUnique: jest.fn().mockResolvedValue(null) },
    ...prisma,
  };
  s.tieneModuloContabilidad = jest.fn().mockResolvedValue(true);
  s.periodos = { resolverPeriodoPorFecha: jest.fn().mockResolvedValue('p') };
  const resolver = (e: string, c: string) =>
    faltan.includes(c) ? Promise.reject(new FaltaMapeoError(e, [c])) : Promise.resolve(cuentaDe(c));
  s.mapeo = conResolvedor(
    {
      getCuentaPorConcepto: jest.fn().mockImplementation(resolver),
      resolverConcepto: jest.fn().mockImplementation((e: string, c: string) =>
        modos[c] === 'SUMAR_A_PRINCIPAL'
          ? Promise.resolve({ modo: 'SUMAR_A_PRINCIPAL' })
          : resolver(e, c).then((id) => ({ modo: 'CUENTA', cuentaId: id })),
      ),
    },
    reglas,
    s.prisma,
  );
  s.mediosPago = {
    resolverPorCodigoMedio: jest.fn().mockResolvedValue(new Map()),
    resolverPorMedio: jest.fn().mockResolvedValue(new Map()),
    idMedioPorCodigo: jest.fn().mockResolvedValue(7),
  };
  s.tipoCambio = { getTasa: jest.fn() };
  s.asientos = { crearConfirmado: jest.fn().mockResolvedValue({}) };
  s.avisosMapeo = { registrarFaltaMapeo: jest.fn().mockResolvedValue(undefined) };
  return s;
};
