import { describe, expect, it, vi } from 'vitest';
import { retryNotionRequest } from '../../../../../src/context/ingest/adapters/notion/notion-client.js';

describe('Notion client retry helper', () => {
  it('retries rate-limited requests and then returns the response', async () => {
    const sleep = vi.fn().mockResolvedValue(undefined);
    const operation = vi
      .fn()
      .mockRejectedValueOnce({ code: 'rate_limited', headers: { 'retry-after': '2' } })
      .mockResolvedValueOnce({ ok: true });

    const result = await retryNotionRequest(operation, { sleep, maxAttempts: 2 });

    expect(result).toEqual({ ok: true });
    expect(sleep).toHaveBeenCalledWith(2000);
  });

  it('caps retry-after sleep from rate-limit responses', async () => {
    const sleep = vi.fn().mockResolvedValue(undefined);
    const operation = vi
      .fn()
      .mockRejectedValueOnce({ code: 'rate_limited', headers: { 'retry-after': '3600' } })
      .mockResolvedValueOnce({ ok: true });

    await retryNotionRequest(operation, { sleep, maxAttempts: 2 });

    expect(sleep).toHaveBeenCalledWith(60_000);
  });

  it('retries transient 5xx requests and then returns the response', async () => {
    const sleep = vi.fn().mockResolvedValue(undefined);
    const operation = vi
      .fn()
      .mockRejectedValueOnce({ code: 'service_unavailable', status: 503, message: 'temporary outage' })
      .mockResolvedValueOnce({ ok: true });

    const result = await retryNotionRequest(operation, { sleep, maxAttempts: 2 });

    expect(result).toEqual({ ok: true });
    expect(sleep).toHaveBeenCalledWith(1000);
  });

  it('uses exponential backoff for transient 5xx retries', async () => {
    const sleep = vi.fn().mockResolvedValue(undefined);
    const operation = vi
      .fn()
      .mockRejectedValueOnce({ code: 'service_unavailable', status: 503, message: 'temporary outage' })
      .mockRejectedValueOnce({ code: 'service_unavailable', status: 503, message: 'temporary outage' })
      .mockResolvedValueOnce({ ok: true });

    await retryNotionRequest(operation, { sleep, maxAttempts: 3 });

    expect(sleep).toHaveBeenNthCalledWith(1, 1000);
    expect(sleep).toHaveBeenNthCalledWith(2, 2000);
  });

  it('throws the sanitized error after attempts are exhausted', async () => {
    const sleep = vi.fn().mockResolvedValue(undefined);
    const operation = vi.fn().mockRejectedValue({ code: 'rate_limited', message: 'token secret leaked' });

    await expect(retryNotionRequest(operation, { sleep, maxAttempts: 2, authToken: 'secret' })).rejects.toThrow(
      /token \*\*\* leaked/,
    );
  });
});
