import { ConflictException, NotFoundException } from '@nestjs/common';
import { RubrosService } from './rubros.service';

describe('RubrosService', () => {
  const crearService = (overrides: Record<string, any> = {}) => {
    const prisma: any = {
      rubros: {
        findFirst: jest.fn().mockResolvedValue(null),
        findMany: jest.fn().mockResolvedValue([]),
        create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'rub-1', ...data })),
        update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'rub-1', ...data })),
      },
      empresas_sucursales: { findMany: jest.fn().mockResolvedValue([]) },
      usuario: { findUnique: jest.fn().mockResolvedValue({ is_superadmin: false }) },
      empresas: { findUnique: jest.fn().mockResolvedValue({ type: 'subsidiary' }) },
      asignacion_sucursal_caja_usuario: { findMany: jest.fn().mockResolvedValue([]) },
      sucursal_rubros: {
        deleteMany: jest.fn().mockResolvedValue({ count: 0 }),
        createMany: jest.fn().mockResolvedValue({ count: 0 }),
      },
      ...overrides,
    };
    prisma.$transaction = jest.fn().mockImplementation((cb: any) => cb(prisma));
    const audit = { log: jest.fn().mockResolvedValue(undefined) };
    return { service: new RubrosService(prisma as any, audit as any), prisma, audit };
  };

  describe('create', () => {
    it('guarda el rubro con el código en mayúsculas y sin espacios', async () => {
      const { service, prisma } = crearService();

      await service.create({ codigo: ' desp ', descripcion: 'Despensa' } as any, 'emp-1', 'user-1');

      const data = prisma.rubros.create.mock.calls[0][0].data;
      expect(data.codigo).toBe('DESP');
      expect(data.empresa_id).toBe('emp-1');
      expect(data.descripcion).toBe('Despensa');
    });

    it('rechaza un código ya usado en la misma empresa', async () => {
      const { service } = crearService({
        rubros: {
          findFirst: jest.fn().mockResolvedValue({ id: 'rub-existente', codigo: 'DESP' }),
          create: jest.fn(),
        },
      });

      await expect(service.create({ codigo: 'DESP', descripcion: 'Otra' } as any, 'emp-1')).rejects.toThrow(
        ConflictException,
      );
    });
  });

  describe('findAll', () => {
    it('devuelve sólo los rubros de la empresa del usuario', async () => {
      const { service, prisma } = crearService();

      await service.findAll('emp-1');

      expect(prisma.rubros.findMany.mock.calls[0][0].where).toEqual({ empresa_id: 'emp-1' });
    });

    it('con soloActivos deja fuera los dados de baja', async () => {
      const { service, prisma } = crearService();

      await service.findAll('emp-1', true);

      expect(prisma.rubros.findMany.mock.calls[0][0].where).toEqual({ empresa_id: 'emp-1', activo: true });
    });
  });

  describe('remove', () => {
    it('da de baja lógica en vez de borrar: hay FKs colgando del rubro', async () => {
      const { service, prisma } = crearService({
        rubros: {
          findFirst: jest.fn().mockResolvedValue({ id: 'rub-1', empresa_id: 'emp-1', descripcion: 'Despensa' }),
          update: jest.fn().mockResolvedValue({ id: 'rub-1', activo: false }),
        },
      });

      await service.remove('rub-1', 'emp-1', 'user-1');

      expect(prisma.rubros.update).toHaveBeenCalledWith(
        expect.objectContaining({ where: { id: 'rub-1' }, data: { activo: false } }),
      );
    });

    it('no deja tocar un rubro de otra empresa', async () => {
      const { service } = crearService({
        rubros: { findFirst: jest.fn().mockResolvedValue(null), update: jest.fn() },
      });

      await expect(service.remove('rub-ajeno', 'emp-1')).rejects.toThrow(NotFoundException);
    });
  });

  describe('setSucursales', () => {
    it('reemplaza el set completo de sucursales del rubro', async () => {
      const { service, prisma } = crearService({
        rubros: { findFirst: jest.fn().mockResolvedValue({ id: 'rub-1', empresa_id: 'emp-1', descripcion: 'Despensa' }) },
        empresas_sucursales: {
          findMany: jest.fn().mockResolvedValue([{ id: 'suc-1' }, { id: 'suc-2' }]),
        },
      });

      await service.setSucursales('rub-1', ['suc-1', 'suc-2'], 'emp-1', 'user-1');

      expect(prisma.sucursal_rubros.deleteMany).toHaveBeenCalledWith({ where: { rubro_id: 'rub-1' } });
      expect(prisma.sucursal_rubros.createMany.mock.calls[0][0].data).toEqual([
        { rubro_id: 'rub-1', sucursal_id: 'suc-1' },
        { rubro_id: 'rub-1', sucursal_id: 'suc-2' },
      ]);
    });

    it('con lista vacía deja el rubro sin sucursales y no llama a createMany', async () => {
      const { service, prisma } = crearService({
        rubros: { findFirst: jest.fn().mockResolvedValue({ id: 'rub-1', empresa_id: 'emp-1', descripcion: 'Despensa' }) },
      });

      await service.setSucursales('rub-1', [], 'emp-1');

      expect(prisma.sucursal_rubros.deleteMany).toHaveBeenCalledWith({ where: { rubro_id: 'rub-1' } });
      expect(prisma.sucursal_rubros.createMany).not.toHaveBeenCalled();
    });

    it('rechaza sucursales que no son de la empresa', async () => {
      const { service } = crearService({
        rubros: { findFirst: jest.fn().mockResolvedValue({ id: 'rub-1', empresa_id: 'emp-1', descripcion: 'Despensa' }) },
        empresas_sucursales: { findMany: jest.fn().mockResolvedValue([{ id: 'suc-1' }]) },
      });

      await expect(service.setSucursales('rub-1', ['suc-1', 'suc-ajena'], 'emp-1')).rejects.toThrow(NotFoundException);
    });
  });

  describe('contextoDeUsuario', () => {
    const asignacion = (sucursalId: string, descripcion: string, rubroIds: string[]) => ({
      sucursal: {
        id: sucursalId,
        descripcion,
        sucursal_rubros: rubroIds.map((rubro_id) => ({ rubro_id })),
      },
    });

    it('sin asignaciones el usuario arranca en todos los rubros', async () => {
      const { service } = crearService();

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.sucursales_asignadas).toEqual([]);
      expect(ctx.sucursal_default_id).toBeNull();
      expect(ctx.rubro_default_id).toBeNull();
    });

    it('devuelve la sucursal asignada y su rubro como default', async () => {
      const { service } = crearService({
        asignacion_sucursal_caja_usuario: {
          findMany: jest.fn().mockResolvedValue([asignacion('suc-1', 'Despensa Centro', ['rub-desp'])]),
        },
        rubros: {
          findMany: jest.fn().mockResolvedValue([{ id: 'rub-desp', codigo: 'DESP', descripcion: 'Despensa' }]),
        },
      });

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.sucursales_asignadas).toEqual([{ id: 'suc-1', descripcion: 'Despensa Centro' }]);
      expect(ctx.sucursal_default_id).toBe('suc-1');
      expect(ctx.rubro_default_id).toBe('rub-desp');
      expect(ctx.rubros_disponibles).toHaveLength(1);
    });

    it('deduplica la sucursal cuando el usuario tiene una asignación por caja', async () => {
      const { service } = crearService({
        asignacion_sucursal_caja_usuario: {
          findMany: jest.fn().mockResolvedValue([
            asignacion('suc-1', 'Despensa Centro', ['rub-desp']),
            asignacion('suc-1', 'Despensa Centro', ['rub-desp']),
          ]),
        },
      });

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.sucursales_asignadas).toEqual([{ id: 'suc-1', descripcion: 'Despensa Centro' }]);
      expect(ctx.rubro_default_id).toBe('rub-desp');
    });

    it('con sucursales de dos rubros no elige ninguno por el usuario', async () => {
      const { service } = crearService({
        asignacion_sucursal_caja_usuario: {
          findMany: jest.fn().mockResolvedValue([
            asignacion('suc-1', 'Despensa Centro', ['rub-desp']),
            asignacion('suc-2', 'Ferretería Sur', ['rub-ferr']),
          ]),
        },
      });

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.sucursales_asignadas).toHaveLength(2);
      expect(ctx.sucursal_default_id).toBe('suc-1');
      expect(ctx.rubro_default_id).toBeNull();
    });

    const rubrosEmpresa = [
      { id: 'rub-desp', codigo: 'DESP', descripcion: 'Despensa' },
      { id: 'rub-ferr', codigo: 'FERR', descripcion: 'Ferretería' },
    ];

    it('asignado a una sucursal sólo ofrece su rubro y no "Todos los rubros"', async () => {
      const { service } = crearService({
        asignacion_sucursal_caja_usuario: {
          findMany: jest.fn().mockResolvedValue([asignacion('suc-2', 'Ferretería Sur', ['rub-ferr'])]),
        },
        rubros: { findMany: jest.fn().mockResolvedValue(rubrosEmpresa) },
      });

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.rubros_disponibles.map((r) => r.id)).toEqual(['rub-ferr']);
      expect(ctx.rubros_todos).toBe(false);
      expect(ctx.rubro_default_id).toBe('rub-ferr');
    });

    it('sin asignaciones ofrece todos los rubros y "Todos los rubros"', async () => {
      const { service } = crearService({
        rubros: { findMany: jest.fn().mockResolvedValue(rubrosEmpresa) },
      });

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.rubros_disponibles).toHaveLength(2);
      expect(ctx.rubros_todos).toBe(true);
    });

    it('un superadmin asignado a una sucursal sigue viendo todos los rubros', async () => {
      const { service } = crearService({
        asignacion_sucursal_caja_usuario: {
          findMany: jest.fn().mockResolvedValue([asignacion('suc-2', 'Ferretería Sur', ['rub-ferr'])]),
        },
        rubros: { findMany: jest.fn().mockResolvedValue(rubrosEmpresa) },
        usuario: { findUnique: jest.fn().mockResolvedValue({ is_superadmin: true }) },
      });

      const ctx = await service.contextoDeUsuario('user-1', 'emp-1');

      expect(ctx.rubros_disponibles).toHaveLength(2);
      expect(ctx.rubros_todos).toBe(true);
    });

    it('sólo ofrece rubros activos de la empresa del usuario', async () => {
      const { service, prisma } = crearService();

      await service.contextoDeUsuario('user-1', 'emp-1');

      expect(prisma.rubros.findMany.mock.calls[0][0].where).toEqual({ empresa_id: 'emp-1', activo: true });
    });
  });
});
