import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { executeMcpCall } from './tool-executor.js';

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

  function baseCtx() {
    return {
      workspacePath: workspace,
      ownerId: 'u1',
      jobId: 'j1',
      config: {
        maxBinarySizeMb: 1,
        maxOutputFilesPerJob: 5,
        maxOutputSizeMbPerJob: 10,
        callTimeoutSeconds: 30,
      },
      quotaState: { files: 0, bytes: 0 },
    };
  }

  it('concatenates text content into output', async () => {
    const fakeClient = {
      callTool: vi.fn().mockResolvedValue({
        content: [
          { type: 'text', text: 'hello ' },
          { type: 'text', text: 'world' },
        ],
      }),
    };
    const res = await executeMcpCall({
      client: fakeClient as never,
      serverId: 'canva',
      toolName: 'ping',
      input: {},
      ctx: baseCtx(),
    });
    expect(res.isError).toBeFalsy();
    expect(res.output).toContain('hello world');
  });

  it('saves image content to output/mcp', async () => {
    const PNG = Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), Buffer.alloc(4, 0)]);
    const fakeClient = {
      callTool: vi.fn().mockResolvedValue({
        content: [{ type: 'image', data: PNG.toString('base64'), mimeType: 'image/png' }],
      }),
    };
    const res = await executeMcpCall({
      client: fakeClient as never,
      serverId: 'canva',
      toolName: 'render',
      input: {},
      ctx: baseCtx(),
    });
    expect(res.isError).toBeFalsy();
    expect(res.output).toMatch(/Saved: output\/mcp\/canva\/render-/);
    // Image must NOT be pushed to images[] (context-bloat guard)
    expect((res as { images?: unknown }).images).toBeUndefined();
  });

  it('returns isError when callTool throws', async () => {
    const fakeClient = {
      callTool: vi.fn().mockRejectedValue(new Error('boom')),
    };
    const res = await executeMcpCall({
      client: fakeClient as never,
      serverId: 'canva',
      toolName: 'x',
      input: {},
      ctx: baseCtx(),
    });
    expect(res.isError).toBe(true);
    expect(res.output).toMatch(/boom/);
  });

  it('writes a raw JSON log under logs/mcp/{serverId}/ on success', async () => {
    const fakeClient = {
      callTool: vi.fn().mockResolvedValue({
        content: [{ type: 'text', text: 'pong' }],
      }),
    };
    await executeMcpCall({
      client: fakeClient as never,
      serverId: 'canva',
      toolName: 'ping',
      input: { q: 'hi' },
      ctx: baseCtx(),
    });
    const files = await fs.readdir(path.join(workspace, 'logs', 'mcp', 'canva'));
    expect(files.some((f) => f.startsWith('ping-') && f.endsWith('.json'))).toBe(true);
    const history = await fs.readFile(path.join(workspace, 'logs', 'mcp-history.jsonl'), 'utf-8');
    expect(history).toMatch(/"toolName":"ping"/);
  });

  it('writes a raw JSON log under logs/mcp/{serverId}/ on failure', async () => {
    const fakeClient = {
      callTool: vi.fn().mockRejectedValue(new Error('boom')),
    };
    await executeMcpCall({
      client: fakeClient as never,
      serverId: 'canva',
      toolName: 'fail',
      input: {},
      ctx: baseCtx(),
    });
    const files = await fs.readdir(path.join(workspace, 'logs', 'mcp', 'canva'));
    expect(files.some((f) => f.startsWith('fail-') && f.endsWith('.json'))).toBe(true);
    const body = JSON.parse(
      await fs.readFile(
        path.join(workspace, 'logs', 'mcp', 'canva', files.find((f) => f.startsWith('fail-'))!),
        'utf-8',
      ),
    );
    expect(body.isError).toBe(true);
    expect(body.output).toMatch(/boom/);
  });
});
