import sharp from 'sharp';
import type { CandidataImagen } from './tipos';
import {
  analizarImagen,
  candidataDesdeSerper,
  descargarImagen,
  hamming,
  marcarDuplicadas,
  MAX_A_VISION,
  ordenarParaVision,
  puntajePrefiltro,
} from './prefiltro-imagenes';

const png = (w: number, h: number, bg: { r: number; g: number; b: number }) =>
  sharp({ create: { width: w, height: h, channels: 3, background: bg } }).png().toBuffer();

describe('puntajePrefiltro', () => {
  const base = { imageUrl: 'u', title: 't', imageWidth: 900, imageHeight: 900 };
  it('bonifica dominio del fabricante y tamaño grande, penaliza marketplaces', () => {
    expect(puntajePrefiltro({ ...base, domain: 'www.makita.com.py' }, 'Makita')).toBe(4);
    expect(puntajePrefiltro({ ...base, domain: 'articulo.mercadolibre.com.py' }, 'Makita')).toBe(-1);
    expect(puntajePrefiltro({ ...base, domain: 'tienda.com', imageWidth: 300 }, null)).toBe(0);
  });
  it('candidataDesdeSerper copia los campos y calcula el prefiltro', () => {
    const c = candidataDesdeSerper({ ...base, domain: 'www.makita.com.py' }, 'Makita');
    expect(c).toMatchObject({ url: 'u', dominio: 'www.makita.com.py', titulo: 't', ancho: 900, alto: 900, prefiltro: 4 });
  });
});

describe('descargarImagen', () => {
  const resp = (ok: boolean, tipo: string, body = Buffer.from('x')) =>
    ({ ok, headers: { get: () => tipo }, arrayBuffer: async () => body }) as unknown as Response;
  it('devuelve null si no es imagen o falla', async () => {
    expect(await descargarImagen('u', async () => resp(true, 'text/html'))).toBeNull();
    expect(await descargarImagen('u', async () => resp(false, 'image/jpeg'))).toBeNull();
    expect(await descargarImagen('u', async () => { throw new Error('timeout'); })).toBeNull();
  });
  it('devuelve el buffer cuando es imagen', async () => {
    const b = await descargarImagen('u', async () => resp(true, 'image/png', Buffer.from('abc')));
    expect(b?.toString()).toBe('abc');
  });
});

describe('analizarImagen', () => {
  it('descarta imágenes chicas o con proporción rara', async () => {
    expect(await analizarImagen(await png(200, 600, { r: 255, g: 255, b: 255 }))).toMatchObject({ descarte: expect.stringContaining('chica') });
    expect(await analizarImagen(await png(1200, 400, { r: 255, g: 255, b: 255 }))).toMatchObject({ descarte: expect.stringContaining('proporción') });
  });
  it('mide el borde blanco y calcula un dhash de 64 bits', async () => {
    const blanca = await analizarImagen(await png(500, 500, { r: 255, g: 255, b: 255 }));
    const roja = await analizarImagen(await png(500, 500, { r: 200, g: 0, b: 0 }));
    expect(blanca).toMatchObject({ ancho: 500, alto: 500 });
    expect((blanca as any).bordeBlanco).toBeGreaterThan(0.95);
    expect((roja as any).bordeBlanco).toBe(0);
    expect((blanca as any).dhash).toHaveLength(64);
  });
  it('devuelve descarte con un buffer que no es imagen', async () => {
    expect(await analizarImagen(Buffer.from('no soy imagen'))).toMatchObject({ descarte: expect.any(String) });
  });
});

describe('hamming y marcarDuplicadas', () => {
  const c = (dhash: string, extra: Partial<CandidataImagen> = {}): CandidataImagen =>
    ({ url: dhash, dominio: '', titulo: '', ancho: 1, alto: 1, prefiltro: 0, dhash, buffer: Buffer.from(''), ...extra });
  it('hamming cuenta bits distintos', () => {
    expect(hamming('0000', '0101')).toBe(2);
  });
  it('marca duplicada la segunda de dos casi iguales, no toca las descartadas', () => {
    const a = c('0'.repeat(64));
    const b = c('0'.repeat(60) + '1111'); // distancia 4 → duplicada
    const d = c('1'.repeat(64));
    const ya = c('0'.repeat(64), { descarte: 'chica' });
    marcarDuplicadas([a, ya, b, d]);
    expect(a.descarte).toBeUndefined();
    expect(b.descarte).toBe('duplicada');
    expect(d.descarte).toBeUndefined();
    expect(ya.descarte).toBe('chica');
  });
});

describe('ordenarParaVision', () => {
  it('prioriza borde blanco y prefiltro, corta en MAX_A_VISION y marca el resto', () => {
    const cands: CandidataImagen[] = Array.from({ length: 14 }, (_, i) => ({
      url: String(i), dominio: '', titulo: '', ancho: 1, alto: 1, prefiltro: i % 3, bordeBlanco: (i % 5) / 5, dhash: '', buffer: Buffer.from(''),
    }));
    cands[0].descarte = 'chica';
    const top = ordenarParaVision(cands);
    expect(top).toHaveLength(MAX_A_VISION);
    expect(top[0].bordeBlanco).toBe(0.8);
    expect(cands.filter((c) => c.descarte === 'fuera del top para visión')).toHaveLength(13 - MAX_A_VISION);
  });
});
