import { ConflictException, NotFoundException } from '@nestjs/common';
import { SugerenciasService } from './sugerencias.service';

const sug = (o: any = {}) => ({
  id: 's1', empresa_id: 'e1', producto_id: 'p1', busqueda_id: 'b1', path: 'sugeridas/e1/p1/x.webp', fuente_url: 'https://f/1',
  fuente_dominio: 'f', fuente_titulo: 't', ancho: 800, alto: 800, coincide: 0.9, fondo_blanco: true, marca_agua: false, calidad: 0.9, score: 0.925,
  nota: null, estado: 'SUGERIDA', revisado_por: null, revisado_en: null, producto_imagen_id: null, created_at: new Date(), ...o,
});

const crear = (over: any = {}) => {
  const prisma: any = {
    producto_imagenes_sugeridas: {
      findFirst: jest.fn().mockResolvedValue(sug()),
      findMany: jest.fn().mockResolvedValue([]),
      update: jest.fn(async ({ where, data }: any) => ({ ...sug(), id: where.id, ...data })),
      updateMany: jest.fn().mockResolvedValue({ count: 2 }),
      groupBy: jest.fn().mockResolvedValue([]),
      count: jest.fn().mockResolvedValue(0),
    },
    producto_imagenes: {
      count: jest.fn().mockResolvedValue(0),
      create: jest.fn(async ({ data }: any) => ({ id: 'img1', ...data })),
    },
    productos: { findMany: jest.fn().mockResolvedValue([]) },
    ...over.prisma,
  };
  prisma.$transaction = jest.fn(async (fn: any) => fn(prisma));
  const storage = {
    promoverAProducto: jest.fn().mockResolvedValue({ path: 'productos/p1/y.webp', tamanhoKb: 40 }),
    borrar: jest.fn().mockResolvedValue(undefined),
    urlPublica: jest.fn((p: string) => `https://cdn/${p}`),
    ...over.storage,
  };
  const audit = { log: jest.fn().mockResolvedValue(undefined) };
  const svc = new SugerenciasService(prisma as any, storage as any, audit as any);
  return { svc, prisma, storage, audit };
};

describe('SugerenciasService.aprobar', () => {
  it('copia el objeto, crea producto_imagenes (principal si no había) y marca APROBADA', async () => {
    const { svc, prisma, storage } = crear();
    const r = await svc.aprobar('e1', 'u1', 's1');
    expect(storage.promoverAProducto).toHaveBeenCalledWith('sugeridas/e1/p1/x.webp', 'p1');
    expect(prisma.producto_imagenes.create).toHaveBeenCalledWith(
      expect.objectContaining({ data: expect.objectContaining({ producto_id: 'p1', path: 'productos/p1/y.webp', extension: 'webp', es_principal: true, active: true }) }),
    );
    expect(prisma.producto_imagenes_sugeridas.update).toHaveBeenCalledWith(
      expect.objectContaining({ where: { id: 's1' }, data: expect.objectContaining({ estado: 'APROBADA', revisado_por: 'u1', producto_imagen_id: 'img1' }) }),
    );
    expect(r.estado).toBe('APROBADA');
  });
  it('no es principal si el producto ya tenía imágenes', async () => {
    const { svc, prisma } = crear();
    prisma.producto_imagenes.count.mockResolvedValue(2);
    await svc.aprobar('e1', 'u1', 's1');
    expect(prisma.producto_imagenes.create.mock.calls[0][0].data.es_principal).toBe(false);
  });
  it('409 si el producto ya tiene 5 imágenes', async () => {
    const { svc, prisma } = crear();
    prisma.producto_imagenes.count.mockResolvedValue(5);
    await expect(svc.aprobar('e1', 'u1', 's1')).rejects.toThrow(ConflictException);
  });
  it('409 si la sugerencia no está SUGERIDA; 404 si no es de la empresa', async () => {
    const { svc, prisma } = crear();
    prisma.producto_imagenes_sugeridas.findFirst.mockResolvedValueOnce(sug({ estado: 'RECHAZADA' }));
    await expect(svc.aprobar('e1', 'u1', 's1')).rejects.toThrow(ConflictException);
    prisma.producto_imagenes_sugeridas.findFirst.mockResolvedValueOnce(null);
    await expect(svc.aprobar('e1', 'u1', 's1')).rejects.toThrow(NotFoundException);
  });
  it('si el objeto ya no existe en Spaces, marca RECHAZADA y lanza 409', async () => {
    const { svc, prisma, storage } = crear();
    storage.promoverAProducto.mockRejectedValueOnce(Object.assign(new Error('nf'), { name: 'NotFound' }));
    await expect(svc.aprobar('e1', 'u1', 's1')).rejects.toThrow(ConflictException);
    expect(prisma.producto_imagenes_sugeridas.update).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ estado: 'RECHAZADA' }) }));
  });
});

describe('SugerenciasService.rechazar', () => {
  it('borra el objeto y marca RECHAZADA', async () => {
    const { svc, storage } = crear();
    const r = await svc.rechazar('e1', 'u1', 's1');
    expect(storage.borrar).toHaveBeenCalledWith('sugeridas/e1/p1/x.webp');
    expect(r.estado).toBe('RECHAZADA');
  });
});

describe('SugerenciasService.aprobarMasivo', () => {
  it('aprueba por producto las aprobables ≥ score_min hasta completar 5, mejores primero', async () => {
    const { svc, prisma } = crear();
    const cands = [
      sug({ id: 'a', producto_id: 'p1', score: 0.95 }),
      sug({ id: 'b', producto_id: 'p1', score: 0.92 }),
      sug({ id: 'c', producto_id: 'p1', score: 0.91, marca_agua: true }), // no aprobable
      sug({ id: 'd', producto_id: 'p2', score: 0.99 }),
    ];
    prisma.producto_imagenes_sugeridas.findMany.mockResolvedValue(cands);
    prisma.producto_imagenes.count.mockImplementation(async ({ where }: any) => (where.producto_id === 'p1' ? 4 : 0));
    const r = await svc.aprobarMasivo('e1', 'u1', { score_min: 0.9 });
    expect(r).toEqual({ aprobadas: 2, productos: 2, omitidas_por_limite: 1 });
    const ids = prisma.producto_imagenes_sugeridas.update.mock.calls.map((c: any) => c[0].where.id);
    expect(ids).toEqual(['a', 'd']);
  });
});

describe('SugerenciasService.rechazarRestantes', () => {
  it('rechaza todas las SUGERIDA del producto y borra sus objetos', async () => {
    const { svc, prisma, storage } = crear();
    prisma.producto_imagenes_sugeridas.findMany.mockResolvedValue([sug({ id: 'x', path: 'p/x' }), sug({ id: 'y', path: 'p/y' })]);
    const r = await svc.rechazarRestantes('e1', 'u1', 'p1');
    expect(r.rechazadas).toBe(2);
    expect(storage.borrar).toHaveBeenCalledTimes(2);
    expect(prisma.producto_imagenes_sugeridas.updateMany).toHaveBeenCalledWith(
      expect.objectContaining({ where: expect.objectContaining({ id: { in: ['x', 'y'] } }), data: expect.objectContaining({ estado: 'RECHAZADA' }) }),
    );
  });
});

describe('SugerenciasService.listar filtros', () => {
  it('varias categorías y marcas se traducen a un IN, y una sola también', async () => {
    const { svc, prisma } = crear();
    prisma.producto_imagenes_sugeridas.groupBy.mockResolvedValue([]);
    await svc.listar('e1', { estado: 'SUGERIDA', categoria_id: ['c1', 'c2'], marca_id: ['m1'] });
    const { where } = prisma.producto_imagenes_sugeridas.groupBy.mock.calls[0][0];
    expect(where.productos.categoria_id).toEqual({ in: ['c1', 'c2'] });
    expect(where.productos.marca_id).toEqual({ in: ['m1'] });
  });
  it('sin categorías ni marcas no agrega esas claves', async () => {
    const { svc, prisma } = crear();
    prisma.producto_imagenes_sugeridas.groupBy.mockResolvedValue([]);
    await svc.listar('e1', { estado: 'SUGERIDA', categoria_id: [], marca_id: undefined });
    const { where } = prisma.producto_imagenes_sugeridas.groupBy.mock.calls[0][0];
    expect(where.productos.categoria_id).toBeUndefined();
    expect(where.productos.marca_id).toBeUndefined();
  });
});

describe('SugerenciasService.listar', () => {
  it('agrupa por producto, pagina por producto y agrega url y aprobable', async () => {
    const { svc, prisma } = crear();
    prisma.producto_imagenes_sugeridas.groupBy.mockResolvedValue([{ producto_id: 'p1' }]);
    prisma.productos.findMany.mockResolvedValue([{ id: 'p1', descripcion: 'HELADERA', cod_producto: 'X', marcas: { descripcion: 'SAMSUNG' }, categoria: { descripcion: 'Heladeras' }, _count: { producto_imagenes: 1 } }]);
    prisma.producto_imagenes_sugeridas.findMany.mockResolvedValue([sug(), sug({ id: 's2', coincide: 0.5, score: 0.6 })]);
    const r = await svc.listar('e1', { estado: 'SUGERIDA', page: 1, limit: 20 });
    expect(r.total).toBe(1);
    expect(r.items[0].producto).toEqual({ id: 'p1', descripcion: 'HELADERA', cod_producto: 'X', marca: 'SAMSUNG', categoria: 'Heladeras', imagenes_actuales: 1 });
    expect(r.items[0].sugerencias[0]).toMatchObject({ id: 's1', url: 'https://cdn/sugeridas/e1/p1/x.webp', aprobable: true });
    expect(r.items[0].sugerencias[1].aprobable).toBe(false);
  });
});
