import { NotFoundException } from '@nestjs/common';
import { AlertasEmpresaService } from './alertas-empresa.service';
import { RegistrarAlertaInput } from './alertas-empresa.types';

describe('AlertasEmpresaService', () => {
  const base: RegistrarAlertaInput = {
    empresaId: 'emp-1',
    clave: 'sifen_evento:factura:fac-1',
    tipo: 'sifen_evento',
    criticidad: 'media',
    titulo: 'Evento sin respuesta',
    descripcion: 'detalle',
    modulo: 'FISCAL',
    privilegioRequerido: 'FIS_SIF_SIFEN_VER',
    sucursalId: 'suc-taller',
    origen: 'sistema',
  };

  const crearService = (overrides: Record<string, any> = {}) => {
    const prisma: any = {
      ai_alertas: {
        findFirst: jest.fn().mockResolvedValue(null),
        findMany: jest.fn().mockResolvedValue([]),
        create: jest.fn().mockResolvedValue({ id: 'al-nueva' }),
        update: jest.fn().mockResolvedValue({ id: 'al-1' }),
        updateMany: jest.fn().mockResolvedValue({ count: 1 }),
      },
      ai_alertas_lecturas: {
        deleteMany: jest.fn().mockResolvedValue({ count: 2 }),
        upsert: jest.fn().mockResolvedValue({}),
        findMany: jest.fn().mockResolvedValue([]),
      },
      usuario: { findUnique: jest.fn().mockResolvedValue({ is_superadmin: false }) },
      empresas: { findUnique: jest.fn().mockResolvedValue({ type: 'subsidiary' }) },
      suscripcion_modulos: { findMany: jest.fn().mockResolvedValue([{ modulos: { codigo: 'FISCAL' } }]) },
      perfiles_privilegios: { findMany: jest.fn().mockResolvedValue([{ privilegios: { codigo: 'FIS_SIF_SIFEN_VER' } }]) },
      asignacion_sucursal_caja_usuario: { findMany: jest.fn().mockResolvedValue([]) },
      empresas_sucursales: { findMany: jest.fn().mockResolvedValue([]) },
      depositos: { findMany: jest.fn().mockResolvedValue([]) },
      ...overrides,
    };
    prisma.$transaction = jest.fn().mockImplementation((cb: any) => cb(prisma));
    return { service: new AlertasEmpresaService(prisma), prisma };
  };

  describe('registrar', () => {
    it('crea la alerta cuando no hay una abierta con esa clave', async () => {
      const { service, prisma } = crearService();

      const r = await service.registrar(base);

      expect(r).toEqual({ id: 'al-nueva', creada: true });
      const data = prisma.ai_alertas.create.mock.calls[0][0].data;
      expect(data).toMatchObject({
        empresa_id: 'emp-1',
        clave: base.clave,
        criticidad: 'media',
        sucursal_id: 'suc-taller',
        privilegio_requerido: 'FIS_SIF_SIFEN_VER',
        resuelta: false,
      });
    });

    it('con una abierta la actualiza en vez de duplicarla', async () => {
      const { service, prisma } = crearService();
      prisma.ai_alertas.findFirst.mockResolvedValue({ id: 'al-1', criticidad: 'media' });

      const r = await service.registrar({ ...base, descripcion: 'otro detalle' });

      expect(r).toEqual({ id: 'al-1', creada: false });
      expect(prisma.ai_alertas.create).not.toHaveBeenCalled();
      expect(prisma.ai_alertas.update.mock.calls[0][0].data.descripcion).toBe('otro detalle');
      expect(prisma.ai_alertas_lecturas.deleteMany).not.toHaveBeenCalled();
    });

    it('si la criticidad sube vuelve a quedar sin leer para todos', async () => {
      const { service, prisma } = crearService();
      prisma.ai_alertas.findFirst.mockResolvedValue({ id: 'al-1', criticidad: 'media' });

      await service.registrar({ ...base, criticidad: 'alta' });

      expect(prisma.ai_alertas_lecturas.deleteMany).toHaveBeenCalledWith({ where: { alerta_id: 'al-1' } });
    });

    it('si otro proceso la creó en paralelo, reintenta como actualización', async () => {
      const { service, prisma } = crearService();
      prisma.ai_alertas.create.mockRejectedValueOnce(Object.assign(new Error('unique'), { code: 'P2002' }));
      prisma.ai_alertas.findFirst.mockResolvedValueOnce(null).mockResolvedValueOnce({ id: 'al-1', criticidad: 'media' });

      const r = await service.registrar(base);

      expect(r).toEqual({ id: 'al-1', creada: false });
    });
  });

  describe('resolver', () => {
    it('marca la alerta abierta como resuelta', async () => {
      const { service, prisma } = crearService();

      expect(await service.resolver('emp-1', base.clave, 'liberada', 'usr-1')).toBe(true);
      const arg = prisma.ai_alertas.updateMany.mock.calls[0][0];
      expect(arg.where).toEqual({ empresa_id: 'emp-1', clave: base.clave, resuelta: false });
      expect(arg.data).toMatchObject({ resuelta: true, resolucion: 'liberada', resuelta_por: 'usr-1' });
    });

    it('sin alerta abierta no hace nada', async () => {
      const { service, prisma } = crearService();
      prisma.ai_alertas.updateMany.mockResolvedValue({ count: 0 });

      expect(await service.resolver('emp-1', base.clave, 'auto')).toBe(false);
    });
  });

  describe('listarParaUsuario', () => {
    const alerta = (id: string, extra: Record<string, unknown> = {}) => ({
      id,
      criticidad: 'media',
      modulo: 'FISCAL',
      privilegio_requerido: 'FIS_SIF_SIFEN_VER',
      sucursal_id: null,
      updated_at: new Date('2026-09-15T10:00:00Z'),
      ...extra,
    });

    it('filtra por privilegio, módulo y sucursal, y marca la lectura del usuario', async () => {
      const { service, prisma } = crearService({
        asignacion_sucursal_caja_usuario: { findMany: jest.fn().mockResolvedValue([{ sucursal_id: 'suc-taller' }]) },
      });
      prisma.ai_alertas.findMany.mockResolvedValue([
        alerta('de-mi-sucursal', { sucursal_id: 'suc-taller' }),
        alerta('de-otra-sucursal', { sucursal_id: 'suc-agogo' }),
        alerta('sin-privilegio', { privilegio_requerido: 'RRHH_LIQ_VER' }),
        alerta('modulo-inactivo', { modulo: 'IMPORTACIONES', privilegio_requerido: null }),
        alerta('de-empresa', { criticidad: 'alta' }),
      ]);
      prisma.ai_alertas_lecturas.findMany.mockResolvedValue([
        { alerta_id: 'de-mi-sucursal', leida_at: new Date(), mostrada_popup_at: null },
      ]);

      const lista = await service.listarParaUsuario('usr-1', 'emp-1');

      expect(lista.map((a) => a.id)).toEqual(['de-empresa', 'de-mi-sucursal']);
      expect(lista.find((a) => a.id === 'de-mi-sucursal')).toMatchObject({ leida: true, mostrada_popup: false });
      expect(lista.find((a) => a.id === 'de-empresa')).toMatchObject({ leida: false });
    });
  });

  describe('marcarLeida', () => {
    it('rechaza una alerta de otra empresa', async () => {
      const { service } = crearService();

      await expect(service.marcarLeida('al-x', 'usr-1', 'emp-1')).rejects.toBeInstanceOf(NotFoundException);
    });

    it('registra la lectura del usuario', async () => {
      const { service, prisma } = crearService();
      prisma.ai_alertas.findFirst.mockResolvedValue({ id: 'al-1' });

      await service.marcarLeida('al-1', 'usr-1', 'emp-1');

      const arg = prisma.ai_alertas_lecturas.upsert.mock.calls[0][0];
      expect(arg.where).toEqual({ alerta_id_usuario_id: { alerta_id: 'al-1', usuario_id: 'usr-1' } });
      expect(arg.create.leida_at).toBeInstanceOf(Date);
    });
  });
});
