import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { saveBinary, type SaveBinaryInput } from './binary-saver.js';

const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const FAKE_PNG = Buffer.concat([PNG_MAGIC, Buffer.alloc(16, 0)]);

describe('saveBinary', () => {
  let workspace: string;

  beforeEach(async () => {
    workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-bin-'));
  });
  afterEach(async () => {
    await fs.rm(workspace, { recursive: true, force: true });
  });

  const baseInput: Omit<SaveBinaryInput, 'bytes' | 'mimeType'> = {
    workspacePath: '',
    serverId: 'canva',
    toolName: 'generate_designs',
    maxBytes: 1024 * 1024,
    jobQuota: { maxFiles: 10, maxBytes: 10 * 1024 * 1024, state: { files: 0, bytes: 0 } },
  };

  it('writes PNG content and returns a workspace-relative path', async () => {
    const res = await saveBinary({
      ...baseInput,
      workspacePath: workspace,
      bytes: FAKE_PNG,
      mimeType: 'image/png',
    });
    expect(res.ok).toBe(true);
    if (res.ok) {
      expect(res.relPath).toMatch(/^output\/mcp\/canva\/generate_designs-/);
      expect(res.relPath.endsWith('.png')).toBe(true);
      const abs = path.join(workspace, res.relPath);
      const stat = await fs.stat(abs);
      expect(stat.size).toBe(FAKE_PNG.length);
    }
  });

  it('rejects when magic bytes do not match mime type', async () => {
    const res = await saveBinary({
      ...baseInput,
      workspacePath: workspace,
      bytes: Buffer.from('not an image'),
      mimeType: 'image/png',
    });
    expect(res.ok).toBe(false);
  });

  it('rejects when exceeding per-file byte limit', async () => {
    const res = await saveBinary({
      ...baseInput,
      workspacePath: workspace,
      bytes: FAKE_PNG,
      mimeType: 'image/png',
      maxBytes: 4,
    });
    expect(res.ok).toBe(false);
  });

  it('rejects when exceeding per-job file quota', async () => {
    const quotaState = { files: 10, bytes: 0 };
    const res = await saveBinary({
      ...baseInput,
      workspacePath: workspace,
      bytes: FAKE_PNG,
      mimeType: 'image/png',
      jobQuota: { maxFiles: 10, maxBytes: 1024, state: quotaState },
    });
    expect(res.ok).toBe(false);
  });

  it('rejects toolName with traversal', async () => {
    const res = await saveBinary({
      ...baseInput,
      toolName: '../../etc/passwd',
      workspacePath: workspace,
      bytes: FAKE_PNG,
      mimeType: 'image/png',
    });
    expect(res.ok).toBe(false);
  });
});
