/**
 * Regression guard — a short video gets a real poster frame, and the keepsake never shows a broken
 * tile.
 *
 * Both the compression worker and the HTML export ran the same invocation:
 *
 *     ffmpeg -i <src> -vframes 1 -ss 00:00:01 -vf scale=… -y <out>
 *
 * `-ss` AFTER `-i` is an output-side seek. Against a clip of a second or less ffmpeg exits **0 and
 * writes nothing**, and both call sites gated on the exit status. So:
 *
 *  - the worker wrote `thumbnail_path` and logged "thumbnail generated" for a file that was never
 *    created → `GET /upload/{id}/thumbnail` 404s in the live feed;
 *  - the export listed `media/…_thumb.jpg` in `data.json` while the ZIP writer skipped the
 *    unopenable file → the keepsake rendered a broken image tile.
 *
 * Any clip at or under a second, which phones produce constantly: mis-taps, Live Photos, boomerangs.
 * Every server-side signal stayed green throughout.
 *
 * Two fixtures on purpose, because they take different paths through the fix:
 *  - `sample.mp4` is exactly 1.000 s. An input-side seek to 1 s is STILL past its last frame, so it
 *    is the 0 s fallback that saves it. Moving `-ss` before `-i` alone does not fix this file.
 *  - `sample-5s.mp4` is 5 s and succeeds on the first seek — the normal path, which no test covered
 *    at all before, because the suite only ever had the boundary fixture.
 */
import { test, expect } from '../../fixtures/test';
import { uploadRaw } from '../../helpers/upload-client';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { BASE } from '../../helpers/env';

const CLIPS = [
  { file: 'sample.mp4', label: '1.000s — needs the 0s fallback' },
  { file: 'sample-5s.mp4', label: '5s — succeeds on the first seek' },
];

async function uploadClip(jwt: string, file: string): Promise<string> {
  const bytes = readFileSync(join(process.cwd(), 'fixtures', 'media', file));
  const res = await uploadRaw(jwt, bytes, { filename: file, contentType: 'video/mp4' });
  expect(res.status, `uploading ${file}`).toBe(201);
  return ((await res.json()) as { id: string }).id;
}

test.describe('Video — the poster frame is real', () => {
  for (const { file, label } of CLIPS) {
    test(`${file} (${label}) gets a fetchable poster`, async ({ guest, db }) => {
      test.setTimeout(60_000);
      const g = await guest(`Poster${file.replace(/\W/g, '')}`);
      const id = await uploadClip(g.jwt, file);
      await expect.poll(() => db.compressionStatus(id), { timeout: 45_000 }).toBe('done');

      // The DB must not claim a thumbnail that isn't there — that claim IS the defect.
      const res = await fetch(`${BASE}/api/v1/upload/${id}/thumbnail`, {
        headers: { Authorization: `Bearer ${g.jwt}` },
      });
      expect(res.status, `${file}: the poster must exist, not just be recorded`).toBe(200);
      expect(res.headers.get('content-type')).toContain('image/');
      expect((await res.arrayBuffer()).byteLength).toBeGreaterThan(0);
    });
  }

  test('a video upload still succeeds even if no poster can be extracted', async ({
    guest,
    db,
  }) => {
    // The mirror that keeps the fix honest. Tightening the check to "the file must exist" without
    // also making a missing poster non-fatal would have been far worse than the bug: the worker's
    // call used `?`, so every sub-second clip would fail compression, exhaust its retries and be
    // soft-deleted. A cosmetic defect turned into data loss.
    //
    // `compression_status = 'done'` with the upload still present is exactly that guarantee.
    const g = await guest('PosterSurvivor');
    const id = await uploadClip(g.jwt, 'sample.mp4');
    await expect.poll(() => db.compressionStatus(id), { timeout: 45_000 }).toBe('done');
    expect(await db.countUploadsForUser(g.userId)).toBe(1);

    // And the video itself is playable regardless of the poster.
    const orig = await fetch(`${BASE}/api/v1/upload/${id}/original`, {
      headers: { Authorization: `Bearer ${g.jwt}` },
    });
    expect(orig.status).toBe(200);
    expect(orig.headers.get('content-type')).toContain('video/');
  });
});
