import { ForbiddenException, NotFoundException } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { OverrideCreditoService, PRIVILEGIO_AUTORIZAR } from './override-credito.service';

const conPrivilegio = (codigo: string) => [
  { perfiles: { perfiles_privilegios: [{ privilegios: { codigo } }] } },
];

describe('OverrideCreditoService', () => {
  let hash: string;

  beforeAll(async () => {
    hash = await bcrypt.hash('1234', 4); // coste bajo: es un test, no producción
  });

  const crear = (over: Record<string, any> = {}) => {
    const prisma: any = {
      usuario: {
        findFirst: jest.fn().mockResolvedValue({
          id: 'sup-1', username: 'supervisora', nombres: 'Ana', apellidos: 'Giménez',
          pin_supervisor: hash, is_superadmin: false,
          usuario_perfiles: conPrivilegio(PRIVILEGIO_AUTORIZAR),
        }),
        findMany: jest.fn().mockResolvedValue([]),
      },
      autorizaciones_caja: { create: jest.fn().mockResolvedValue({ id: 'aut-1' }) },
      ...over,
    };
    return { service: new OverrideCreditoService(prisma), prisma };
  };

  describe('verificar', () => {
    it('con PIN correcto y privilegio devuelve al supervisor', async () => {
      const { service } = crear();
      const r = await service.verificar('emp-1', 'sup-1', '1234');
      expect(r).toMatchObject({ id: 'sup-1', username: 'supervisora' });
    });

    /** Sin saber quién autoriza habría que probar el PIN contra toda la empresa. */
    it('sin supervisor_id pide indicarlo', async () => {
      const { service } = crear();
      await expect(service.verificar('emp-1', undefined, '1234')).rejects.toMatchObject({
        response: { requiere: 'supervisor_id' },
      });
    });

    it('un PIN incorrecto es 403', async () => {
      const { service } = crear();
      await expect(service.verificar('emp-1', 'sup-1', '9999')).rejects.toThrow(ForbiddenException);
    });

    it('un supervisor de otra empresa no existe', async () => {
      const { service, prisma } = crear({ usuario: { findFirst: jest.fn().mockResolvedValue(null) } });
      await expect(service.verificar('emp-1', 'sup-x', '1234')).rejects.toThrow(NotFoundException);
      expect(prisma.usuario.findFirst.mock.calls[0][0].where).toMatchObject({ empresa_id: 'emp-1' });
    });

    it('sin PIN configurado avisa de quién se trata', async () => {
      const { service } = crear({
        usuario: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'sup-1', username: 'juan', pin_supervisor: null, is_superadmin: false, usuario_perfiles: [],
          }),
        },
      });
      await expect(service.verificar('emp-1', 'sup-1', '1234')).rejects.toThrow(/juan/);
    });

    /** El PIN correcto no alcanza: autorizar crédito es una decisión con permiso propio. */
    it('con PIN correcto pero sin el privilegio no autoriza', async () => {
      const { service } = crear({
        usuario: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'sup-1', username: 'cajera', pin_supervisor: hash, is_superadmin: false,
            usuario_perfiles: conPrivilegio('POS_SUPERVISOR'),
          }),
        },
      });
      await expect(service.verificar('emp-1', 'sup-1', '1234')).rejects.toThrow(/privilegio/);
    });

    it('el superadmin no necesita el privilegio', async () => {
      const { service } = crear({
        usuario: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'sup-1', username: 'root', pin_supervisor: hash, is_superadmin: true, usuario_perfiles: [],
          }),
        },
      });
      await expect(service.verificar('emp-1', 'sup-1', '1234')).resolves.toMatchObject({ id: 'sup-1' });
    });

    it('no persiste nada: verificar sólo verifica', async () => {
      const { service, prisma } = crear();
      await service.verificar('emp-1', 'sup-1', '1234');
      expect(prisma.autorizaciones_caja.create).not.toHaveBeenCalled();
    });
  });

  describe('registrar', () => {
    it('deja la autorización aprobada con el contexto de lo que se autorizó', async () => {
      const { service, prisma } = crear();
      const id = await service.registrar('emp-1', 'u-1', 'sup-1', {
        oportunidad_id: 'opo-1', cliente_id: 'cli-1', total: 11_000_000, motivo: 'Cierre OPP-124',
      });

      expect(id).toBe('aut-1');
      const data = prisma.autorizaciones_caja.create.mock.calls[0][0].data;
      expect(data).toMatchObject({
        empresa_id: 'emp-1',
        // Reusa el tipo del POS: es la misma decisión comercial.
        tipo: 'venta_credito',
        solicitante_id: 'u-1',
        autorizador_id: 'sup-1',
        estado: 'aprobada',
      });
      expect(data.contexto).toMatchObject({ oportunidad_id: 'opo-1', total: 11_000_000 });
      expect(data.resolved_at).toBeInstanceOf(Date);
    });
  });

  describe('supervisores', () => {
    /** Ofrecer a alguien sin PIN manda al vendedor a buscar a quien no puede firmar. */
    it('sólo devuelve usuarios activos con PIN y privilegio', async () => {
      const { service, prisma } = crear();
      await service.supervisores('emp-1');

      const where = prisma.usuario.findMany.mock.calls[0][0].where;
      expect(where).toMatchObject({ empresa_id: 'emp-1', active: true });
      expect(where.pin_supervisor).toEqual({ not: null });
      expect(JSON.stringify(where.OR)).toContain(PRIVILEGIO_AUTORIZAR);
    });

    it('arma el nombre completo y cae al usuario si no lo tiene', async () => {
      const { service } = crear({
        usuario: {
          findMany: jest.fn().mockResolvedValue([
            { id: 'a', username: 'ana', nombres: 'Ana', apellidos: 'Giménez' },
            { id: 'b', username: 'root', nombres: null, apellidos: null },
          ]),
        },
      });
      expect(await service.supervisores('emp-1')).toEqual([
        { id: 'a', username: 'ana', nombre: 'Ana Giménez' },
        { id: 'b', username: 'root', nombre: 'root' },
      ]);
    });
  });
});
