import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createAvatarLoader, type AvatarCache } from '../src/lib/avatar-cache.ts';

function memoryCache(): AvatarCache & { entries: Map<string, Response> } {
  const entries = new Map<string, Response>();
  return {
    entries,
    async match(key) {
      return entries.get(key)?.clone();
    },
    async put(key, response) {
      entries.set(key, response.clone());
    }
  };
}

function image(body = 'avatar', status = 200): Response {
  return new Response(body, { status, headers: { 'Content-Type': 'image/png' } });
}

test('avatar cache coalesces simultaneous misses and persists results', async () => {
  const store = memoryCache();
  let loads = 0;
  let release: ((response: Response) => void) | undefined;
  let signalLoadStarted: (() => void) | undefined;
  const loadStarted = new Promise<void>((resolve) => (signalLoadStarted = resolve));
  const loader = createAvatarLoader(async () => store, async () => {
    loads += 1;
    signalLoadStarted?.();
    return new Promise((resolve) => (release = resolve));
  });

  const first = loader('/api/media?mxc=avatar', 'avatar');
  const second = loader('/api/media?mxc=avatar', 'avatar');
  await loadStarted;
  assert.equal(loads, 1);
  release?.(image());
  assert.equal(await (await first).text(), 'avatar');
  assert.equal(await (await second).text(), 'avatar');

  const nextWorker = createAvatarLoader(async () => store, async () => {
    loads += 1;
    return image('unexpected');
  });
  assert.equal(await (await nextWorker('/api/media?mxc=avatar', 'avatar')).text(), 'avatar');
  assert.equal(loads, 1);
});

test('avatar cache retries unsuccessful responses', async () => {
  const store = memoryCache();
  let loads = 0;
  const loader = createAvatarLoader(async () => store, async () => ++loads === 1 ? image('missing', 404) : image());
  assert.equal((await loader('/avatar', 'avatar')).status, 404);
  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
  assert.equal(loads, 2);
});

test('avatar cache read failures still return the image', async () => {
  let loads = 0;
  const cache: AvatarCache = {
    async match() {
      throw new Error('read failed');
    },
    async put() {}
  };
  const loader = createAvatarLoader(async () => cache, async () => {
    loads += 1;
    return image();
  });

  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
  assert.equal(loads, 1);
});

test('avatar cache write failures still return the image', async () => {
  let loads = 0;
  const cache: AvatarCache = {
    async match() {
      return undefined;
    },
    async put() {
      throw new Error('write failed');
    }
  };
  const loader = createAvatarLoader(async () => cache, async () => {
    loads += 1;
    return image();
  });

  assert.equal(await (await loader('/avatar', 'avatar')).text(), 'avatar');
  assert.equal(loads, 1);
});
