import { describe, expect, it, vi, beforeEach } from 'vitest';

const { ensureImpl } = vi.hoisted(() => ({
  ensureImpl: vi.fn(
    async (ref: { fetchCoverArtId: string }, _tier: number, _priority: string) => {
      await new Promise(r => setTimeout(r, 2));
      return { hit: true, path: `/tmp/${ref.fetchCoverArtId}.webp`, tier: 128 };
    },
  ),
}));

vi.mock('../api/coverCache', () => ({
  coverCacheEnsure: ensureImpl,
  libraryCoverBackfillConfigure: vi.fn(async () => {}),
  libraryCoverBackfillSetUiPriority: vi.fn(async () => {}),
}));

import { coverArtRef } from './ref';
import {
  __test_resetCoverTraffic,
  coverTrafficBeginServerSwitch,
  coverTrafficEndServerSwitch,
} from './coverTraffic';
import {
  __test_queuedCoverIds,
  __test_resetCoverEnsureQueue,
  coverEnsureBump,
  coverEnsureQueued,
  coverEnsureRelease,
  coverEnsureReprioritize,
  coverEnsureQueueStats,
} from './ensureQueue';

describe('coverEnsureQueued', () => {
  beforeEach(() => {
    __test_resetCoverEnsureQueue();
    __test_resetCoverTraffic();
    ensureImpl.mockClear();
    ensureImpl.mockImplementation(
      async (ref: { fetchCoverArtId: string }, _tier: number, _priority: string) => {
        await new Promise(r => setTimeout(r, 2));
        return { hit: true, path: `/tmp/${ref.fetchCoverArtId}.webp`, tier: 128 };
      },
    );
  });

  it('dedupes concurrent ensures for the same storage key', async () => {
    const ref = coverArtRef('al-1');
    const [a, b] = await Promise.all([
      coverEnsureQueued('s:cover:al-1:128', ref, 128, 'high'),
      coverEnsureQueued('s:cover:al-1:128', ref, 128, 'low'),
    ]);
    expect(a.path).toBe('/tmp/al-1.webp');
    expect(b.path).toBe('/tmp/al-1.webp');
    expect(ensureImpl).toHaveBeenCalledTimes(1);
  });

  it('bumps a queued job ahead of older high-priority work', () => {
    coverTrafficBeginServerSwitch();
    const refA = coverArtRef('al-a');
    const refB = coverArtRef('al-b');
    const refC = coverArtRef('al-c');

    void coverEnsureQueued('s:cover:al-a:128', refA, 128, 'high');
    void coverEnsureQueued('s:cover:al-b:128', refB, 128, 'high');
    void coverEnsureQueued('s:cover:al-c:128', refC, 128, 'high');
    coverEnsureBump('s:cover:al-c:128', 'high');

    expect(__test_queuedCoverIds()[0]).toBe('al-c');
    coverTrafficEndServerSwitch();
  });

  it('reprioritize downgrades viewport-leavers to middle with LIFO order', () => {
    coverTrafficBeginServerSwitch();
    const refNear = coverArtRef('al-near');
    const refFar = coverArtRef('al-far');

    void coverEnsureQueued('s:cover:al-near:128', refNear, 128, 'high');
    void coverEnsureQueued('s:cover:al-far:128', refFar, 128, 'high');
    coverEnsureReprioritize('s:cover:al-near:128', 'middle');
    coverEnsureReprioritize('s:cover:al-far:128', 'middle');

    const stats = coverEnsureQueueStats();
    expect(stats.queuedHigh).toBe(0);
    expect(stats.queuedMiddle).toBe(2);
    expect(__test_queuedCoverIds()[0]).toBe('al-far');
    coverTrafficEndServerSwitch();
  });

  it('shares one invoke slot per cover art id while duplicate jobs wait', async () => {
    let release!: () => void;
    const gate = new Promise<void>(resolve => {
      release = resolve;
    });
    ensureImpl.mockImplementationOnce(async () => {
      await gate;
      return { hit: true, path: '/tmp/al-shared.webp', tier: 128 };
    });

    const ref = coverArtRef('al-shared');
    void coverEnsureQueued('s:cover:al-shared:128', ref, 128, 'high');
    void coverEnsureQueued('s:cover:al-shared:256', ref, 256, 'high');

    await new Promise(r => setTimeout(r, 10));
    expect(ensureImpl).toHaveBeenCalledTimes(1);
    expect(coverEnsureQueueStats().inflight).toBe(1);

    release();
    await new Promise(r => setTimeout(r, 10));
  });

  it('release drops a pending job so a remount can re-queue', async () => {
    coverTrafficBeginServerSwitch();
    const ref = coverArtRef('al-drop');
    const pending = coverEnsureQueued('s:cover:al-drop:128', ref, 128, 'middle');
    coverEnsureRelease('s:cover:al-drop:128');
    const result = await pending;
    expect(result.path).toBe('');
    expect(ensureImpl).not.toHaveBeenCalled();

    coverTrafficEndServerSwitch();
    await new Promise(r => setTimeout(r, 800));

    await coverEnsureQueued('s:cover:al-drop:128', ref, 128, 'high');
    expect(ensureImpl).toHaveBeenCalledTimes(1);
  });
});
