import { BuscadorSerperService } from './buscador-serper.service';

jest.mock('src/config', () => ({ envs: { serperApiKey: 'k' } }));

const redisMock = () => {
  const store = new Map<string, unknown>();
  return {
    get: jest.fn(async (k: string) => store.get(k)),
    set: jest.fn(async (k: string, v: unknown) => void store.set(k, v)),
    store,
  };
};
const okResp = (images: unknown[]) => ({ ok: true, status: 200, json: async () => ({ images }), text: async () => '' }) as unknown as Response;

/** Nest solo inyecta RedisService; fetch y la espera se pisan como propiedades para el test. */
const crear = (redis: ReturnType<typeof redisMock>, fetchFn: jest.Mock, esperaMs = 0) => {
  const svc = new BuscadorSerperService(redis as any);
  svc.fetchFn = fetchFn as unknown as typeof fetch;
  svc.esperaReintentoMs = esperaMs;
  return svc;
};

describe('BuscadorSerperService', () => {
  it('llama a Serper con gl=py/hl=es/num=10 y cachea el resultado', async () => {
    const redis = redisMock();
    const fetchFn = jest.fn(async () => okResp([{ imageUrl: 'u', title: 't', domain: 'd', imageWidth: 1, imageHeight: 1, extra: 'x' }]));
    const svc = crear(redis, fetchFn);

    const r1 = await svc.buscar('makita hp333');
    expect(r1.desdeCache).toBe(false);
    expect(r1.resultados).toEqual([{ imageUrl: 'u', title: 't', domain: 'd', imageWidth: 1, imageHeight: 1 }]);
    const [url, init] = fetchFn.mock.calls[0] as unknown as [string, RequestInit & { headers: Record<string, string> }];
    expect(url).toBe('https://google.serper.dev/images');
    expect(JSON.parse(init.body as string)).toEqual({ q: 'makita hp333', gl: 'py', hl: 'es', num: 10 });
    expect(init.headers['X-API-KEY']).toBe('k');

    const r2 = await svc.buscar('makita hp333');
    expect(r2.desdeCache).toBe(true);
    expect(fetchFn).toHaveBeenCalledTimes(1);
  });

  it('reintenta ante 429/5xx y lanza tras 3 intentos', async () => {
    const redis = redisMock();
    const fetchFn = jest
      .fn()
      .mockResolvedValueOnce({ ok: false, status: 429, text: async () => 'rate' })
      .mockResolvedValueOnce({ ok: false, status: 503, text: async () => 'down' })
      .mockResolvedValueOnce(okResp([]));
    const svc = crear(redis, fetchFn);
    expect((await svc.buscar('x')).resultados).toEqual([]);
    expect(fetchFn).toHaveBeenCalledTimes(3);

    const siempreMal = jest.fn(async () => ({ ok: false, status: 500, text: async () => 'boom' }));
    await expect(crear(redis, siempreMal).buscar('y')).rejects.toThrow('Serper 500');
  });

  it('no reintenta ante 4xx que no sea 429', async () => {
    const fetchFn = jest.fn(async () => ({ ok: false, status: 401, text: async () => 'bad key' }));
    await expect(crear(redisMock(), fetchFn).buscar('z')).rejects.toThrow('Serper 401');
    expect(fetchFn).toHaveBeenCalledTimes(1);
  });

  it('disponible() depende de la key de plataforma', () => {
    expect(crear(redisMock(), jest.fn()).disponible()).toBe(true);
  });
});
