import { BadRequestException, NotFoundException } from '@nestjs/common';
import { FechasEspecialesService } from './fechas-especiales.service';
import { FECHAS_COMERCIALES_PY } from './fechas-comerciales.seed';

const HOY = new Date(2026, 9, 6); // 6 de octubre de 2026

const FECHA = (over: Record<string, any> = {}) => ({
  id: 'f-1', empresa_id: 'emp-1', cliente_id: 'cli-1', lead_id: null,
  tipo: 'cumpleanos', descripcion: null, destinatario_nombre: null,
  dia: 13, mes: 10, recordar_dias_antes: 7, canal_preferido: null,
  activo: true, ultimo_aviso_anio: null,
  clientes: { personas: { razon_social: 'Kiosco Ana' }, cobrador: { id: 'v-1', usuario_id: 'u-9' } },
  crm_leads: null,
  ...over,
});

describe('FechasEspecialesService', () => {
  const crear = (over: Record<string, any> = {}) => {
    const prisma: any = {
      crm_fechas_especiales: {
        findMany: jest.fn().mockResolvedValue([FECHA()]),
        findFirst: jest.fn().mockResolvedValue(FECHA()),
        create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'f-nueva', ...data })),
        update: jest.fn().mockResolvedValue({}),
        delete: jest.fn().mockResolvedValue({}),
      },
      crm_fechas_comerciales: {
        findMany: jest.fn().mockResolvedValue([]),
        findFirst: jest.fn().mockResolvedValue({ id: 'c-1', empresa_id: 'emp-1' }),
        count: jest.fn().mockResolvedValue(6),
        createMany: jest.fn().mockResolvedValue({ count: 0 }),
        upsert: jest.fn().mockImplementation(({ create }) => Promise.resolve({ id: 'c-1', ...create })),
        update: jest.fn().mockResolvedValue({}),
        delete: jest.fn().mockResolvedValue({}),
      },
      crm_actividades: { create: jest.fn().mockResolvedValue({ id: 'act-1' }) },
      clientes: { findFirst: jest.fn().mockResolvedValue({ id: 'cli-1' }) },
      crm_leads: { findFirst: jest.fn().mockResolvedValue({ id: 'lead-1' }) },
      ...over,
    };
    return { service: new FechasEspecialesService(prisma), prisma };
  };

  describe('create', () => {
    it('una fecha cuelga de un cliente o de un lead, no de ambos', async () => {
      const { service } = crear();
      const dto = { tipo: 'cumpleanos', dia: 13, mes: 10, cliente_id: 'cli-1', lead_id: 'lead-1' } as any;
      await expect(service.create(dto, 'emp-1')).rejects.toThrow(BadRequestException);
    });

    it('ni de ninguno', async () => {
      const { service } = crear();
      await expect(service.create({ tipo: 'cumpleanos', dia: 13, mes: 10 } as any, 'emp-1'))
        .rejects.toThrow(BadRequestException);
    });

    it('el cliente tiene que ser de la empresa', async () => {
      const { service } = crear({ clientes: { findFirst: jest.fn().mockResolvedValue(null) } });
      await expect(service.create({ tipo: 'cumpleanos', dia: 13, mes: 10, cliente_id: 'cli-x' } as any, 'emp-1'))
        .rejects.toThrow(NotFoundException);
    });

    it('guarda con 7 días de anticipación por defecto', async () => {
      const { service, prisma } = crear();
      await service.create({ tipo: 'cumpleanos', dia: 13, mes: 10, cliente_id: 'cli-1' } as any, 'emp-1', 'u-1');
      expect(prisma.crm_fechas_especiales.create.mock.calls[0][0].data).toMatchObject({
        recordar_dias_antes: 7, anio: null, created_by: 'u-1',
      });
    });
  });

  describe('agenda', () => {
    it('mezcla fechas de clientes y comerciales ordenadas por cercanía', async () => {
      const { service } = crear({
        crm_fechas_especiales: {
          findMany: jest.fn().mockResolvedValue([
            { ...FECHA(), id: 'f-lejana', dia: 25, mes: 12 },
            { ...FECHA(), id: 'f-cercana', dia: 8, mes: 10 },
          ]),
        },
        crm_fechas_comerciales: { findMany: jest.fn().mockResolvedValue([{ id: 'c-1', nombre: 'Navidad', dia: 25, mes: 12 }]) },
      });
      const r = await service.agenda('emp-1', 400);
      expect(r.data.map((i: any) => i.id)).toEqual(['f-cercana', 'f-lejana', 'c-1']);
      expect(r.data[0].clase).toBe('especial');
    });

    it('recorta por la ventana de días pedida', async () => {
      const { service } = crear({
        crm_fechas_especiales: {
          findMany: jest.fn().mockResolvedValue([{ ...FECHA(), dia: 25, mes: 12 }]),
        },
      });
      expect((await service.agenda('emp-1', 7)).data).toHaveLength(0);
    });

    it('usa el destinatario explícito antes que la razón social', async () => {
      const { service } = crear({
        crm_fechas_especiales: { findMany: jest.fn().mockResolvedValue([FECHA({ destinatario_nombre: 'Sofía' })]) },
      });
      expect((await service.agenda('emp-1')).data[0].destinatario).toBe('Sofía');
    });
  });

  describe('calendario comercial', () => {
    /** Nadie debería tener que cargar la Navidad a mano. */
    it('la primera vez siembra el calendario paraguayo', async () => {
      const { service, prisma } = crear({
        crm_fechas_comerciales: {
          count: jest.fn().mockResolvedValue(0),
          createMany: jest.fn().mockResolvedValue({ count: FECHAS_COMERCIALES_PY.length }),
          findMany: jest.fn().mockResolvedValue([]),
        },
      });
      await service.findComerciales('emp-1');
      expect(prisma.crm_fechas_comerciales.createMany).toHaveBeenCalled();
      const sembradas = prisma.crm_fechas_comerciales.createMany.mock.calls[0][0];
      expect(sembradas.skipDuplicates).toBe(true);
      expect(sembradas.data).toHaveLength(FECHAS_COMERCIALES_PY.length);
    });

    it('si ya hay calendario no vuelve a sembrar', async () => {
      const { service, prisma } = crear();
      await service.findComerciales('emp-1');
      expect(prisma.crm_fechas_comerciales.createMany).not.toHaveBeenCalled();
    });

    it('el Día de la Madre paraguayo es el 15 de mayo, no el segundo domingo', () => {
      const madre = FECHAS_COMERCIALES_PY.find((f) => f.nombre.includes('Madre'));
      expect(madre).toMatchObject({ dia: 15, mes: 5 });
    });

    it('el upsert usa el nombre como clave, así que no duplica', async () => {
      const { service, prisma } = crear();
      await service.upsertComercial({ nombre: ' Navidad ', dia: 25, mes: 12 }, 'emp-1');
      expect(prisma.crm_fechas_comerciales.upsert.mock.calls[0][0].where).toEqual({
        empresa_id_nombre: { empresa_id: 'emp-1', nombre: 'Navidad' },
      });
    });
  });

  describe('generarAvisos', () => {
    it('crea la actividad en la agenda del responsable del cliente', async () => {
      const { service, prisma } = crear();
      const r = await service.generarAvisos(HOY);

      expect(r).toEqual({ especiales: 1, comerciales: 0 });
      const act = prisma.crm_actividades.create.mock.calls[0][0].data;
      expect(act).toMatchObject({ tipo: 'tarea', cliente_id: 'cli-1', vendedor_id: 'v-1', usuario_id: 'u-9' });
      expect(act.asunto).toContain('Kiosco Ana');
    });

    /** En la agenda tiene que aparecer el día que hay que saludar, no hoy. */
    it('programa la actividad para el día del evento', async () => {
      const { service, prisma } = crear();
      await service.generarAvisos(HOY);
      expect(prisma.crm_actividades.create.mock.calls[0][0].data.fecha_programada).toEqual(new Date(2026, 9, 13));
    });

    it('marca el año para no repetir el aviso en la misma temporada', async () => {
      const { service, prisma } = crear();
      await service.generarAvisos(HOY);
      expect(prisma.crm_fechas_especiales.update.mock.calls[0][0].data).toEqual({ ultimo_aviso_anio: 2026 });
    });

    it('una fecha ya avisada este año se saltea', async () => {
      const { service, prisma } = crear({
        crm_fechas_especiales: {
          findMany: jest.fn().mockResolvedValue([FECHA({ ultimo_aviso_anio: 2026 })]),
          update: jest.fn(),
        },
      });
      expect(await service.generarAvisos(HOY)).toEqual({ especiales: 0, comerciales: 0 });
      expect(prisma.crm_actividades.create).not.toHaveBeenCalled();
    });

    it('una fecha fuera de la ventana no genera nada', async () => {
      const { service } = crear({
        crm_fechas_especiales: { findMany: jest.fn().mockResolvedValue([FECHA({ dia: 25, mes: 12 })]), update: jest.fn() },
      });
      expect((await service.generarAvisos(HOY)).especiales).toBe(0);
    });

    it('el canal preferido define el tipo de actividad', async () => {
      const { service, prisma } = crear({
        crm_fechas_especiales: { findMany: jest.fn().mockResolvedValue([FECHA({ canal_preferido: 'visita' })]), update: jest.fn() },
      });
      await service.generarAvisos(HOY);
      expect(prisma.crm_actividades.create.mock.calls[0][0].data.tipo).toBe('visita');
    });

    /** Repartirla por cliente generaría cientos de tareas idénticas. */
    it('una fecha comercial genera una sola actividad sin cliente', async () => {
      const { service, prisma } = crear({
        crm_fechas_especiales: { findMany: jest.fn().mockResolvedValue([]), update: jest.fn() },
        crm_fechas_comerciales: {
          findMany: jest.fn().mockResolvedValue([{ id: 'c-1', empresa_id: 'emp-1', nombre: 'Navidad', dia: 13, mes: 10, recordar_dias_antes: 14, ultimo_aviso_anio: null }]),
          update: jest.fn().mockResolvedValue({}),
        },
      });
      const r = await service.generarAvisos(HOY);
      expect(r).toEqual({ especiales: 0, comerciales: 1 });
      const act = prisma.crm_actividades.create.mock.calls[0][0].data;
      expect(act.cliente_id).toBeUndefined();
      expect(act.asunto).toContain('Navidad');
      expect(act.asunto).toContain('campaña');
    });

    it('para un lead usa el vendedor asignado', async () => {
      const { service, prisma } = crear({
        crm_fechas_especiales: {
          findMany: jest.fn().mockResolvedValue([FECHA({
            cliente_id: null, lead_id: 'lead-1', clientes: null,
            crm_leads: { nombre: 'Luis Pérez', asignado_a: 'v-7' },
          })]),
          update: jest.fn(),
        },
      });
      await service.generarAvisos(HOY);
      const act = prisma.crm_actividades.create.mock.calls[0][0].data;
      expect(act).toMatchObject({ lead_id: 'lead-1', vendedor_id: 'v-7', usuario_id: null });
      expect(act.asunto).toContain('Luis Pérez');
    });
  });
});
