import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { mkdtemp, mkdir, stat, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import type { APIRoute } from 'astro';
let workDir: string;
let POST: APIRoute;

async function setupTmpRoot() {
  workDir = await mkdtemp(join(tmpdir(), 'portfolio-upload-'));
  await mkdir(join(workDir, 'data', 'uploads'), { recursive: true });
  vi.spyOn(process, 'cwd').mockReturnValue(workDir);
  vi.resetModules();
  const mod = await import('../../src/pages/api/upload');
  POST = mod.POST as APIRoute;
}

async function cleanup() {
  vi.restoreAllMocks();
  if (workDir) await rm(workDir, { recursive: true, force: true });
}

beforeEach(setupTmpRoot);
afterEach(cleanup);

function call(form: FormData) {
  const request = new Request('http://localhost/api/upload', { method: 'POST', body: form });
  return POST({ request } as unknown as Parameters<APIRoute>[0]) as Promise<Response>;
}

describe('POST /api/upload', () => {
  it('400 no_file quand aucun fichier', async () => {
    const res = await call(new FormData());
    expect(res.status).toBe(400);
    expect(await res.json()).toEqual({ error: 'no_file' });
  });

  it('400 invalid_extension sur .exe', async () => {
    const form = new FormData();
    form.append('file', new File(['x'], 'malware.exe', { type: 'application/octet-stream' }));
    const res = await call(form);
    expect(res.status).toBe(400);
    expect(await res.json()).toEqual({ error: 'invalid_extension' });
  });

  it('400 invalid_extension sur .svg (retiré pour anti-XSS)', async () => {
    const form = new FormData();
    form.append('file', new File(['<svg/>'], 'evil.svg', { type: 'image/svg+xml' }));
    const res = await call(form);
    expect(res.status).toBe(400);
    expect(await res.json()).toEqual({ error: 'invalid_extension' });
  });

  it('400 file_too_large quand image > 5 MB', async () => {
    const form = new FormData();
    const bigBuffer = new Uint8Array(5 * 1024 * 1024 + 1);
    form.append('file', new File([bigBuffer], 'big.png', { type: 'image/png' }));
    const res = await call(form);
    expect(res.status).toBe(400);
    expect(await res.json()).toEqual({ error: 'file_too_large' });
  });

  it('200 ok sur une image PNG valide', async () => {
    const form = new FormData();
    form.append('file', new File([new Uint8Array(1024)], 'photo.png', { type: 'image/png' }));
    const res = await call(form);
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.ok).toBe(true);
    expect(body.url).toBe('/api/files/img/photo.png');
    const written = await stat(join(workDir, 'data', 'uploads', 'img', 'photo.png'));
    expect(written.size).toBe(1024);
  });

  it('200 ok sur un PDF avec kind=document → bucket assets', async () => {
    const form = new FormData();
    form.append('file', new File([new Uint8Array(2048)], 'cv.pdf', { type: 'application/pdf' }));
    form.append('kind', 'document');
    const res = await call(form);
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.url).toBe('/api/files/assets/cv.pdf');
    const written = await stat(join(workDir, 'data', 'uploads', 'assets', 'cv.pdf'));
    expect(written.size).toBe(2048);
  });

  it('sanitize folder : "evil/../etc" → "eviletc" (caractères non alphanum retirés)', async () => {
    const form = new FormData();
    form.append('file', new File([new Uint8Array(10)], 'a.png', { type: 'image/png' }));
    form.append('folder', 'evil/../etc');
    const res = await call(form);
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.url).toBe('/api/files/img/eviletc/a.png');
    const written = await stat(join(workDir, 'data', 'uploads', 'img', 'eviletc', 'a.png'));
    expect(written.size).toBe(10);
  });
});
