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 { saveMcpRaw } from './raw-logger.js';

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

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

  async function listMcpDir(serverId: string): Promise<string[]> {
    try {
      return await fs.readdir(path.join(workspace, 'logs', 'mcp', serverId));
    } catch {
      return [];
    }
  }

  it('writes a JSON file under logs/mcp/{serverId}/ with the expected structure', async () => {
    saveMcpRaw({
      workspacePath: workspace,
      serverId: 'canva',
      toolName: 'generate_designs',
      args: { prompt: 'cat' },
      content: [{ type: 'text', text: 'hello' }],
      isError: false,
      output: 'hello',
      savedPaths: [],
    });

    const files = await listMcpDir('canva');
    expect(files.length).toBe(1);
    expect(files[0]).toMatch(/^generate_designs-.*\.json$/);

    const body = JSON.parse(
      await fs.readFile(path.join(workspace, 'logs', 'mcp', 'canva', files[0]), 'utf-8'),
    );
    expect(body.serverId).toBe('canva');
    expect(body.toolName).toBe('generate_designs');
    expect(body.arguments).toEqual({ prompt: 'cat' });
    expect(body.isError).toBe(false);
    expect(body.content).toEqual([{ type: 'text', text: 'hello' }]);
    expect(body.output).toBe('hello');
    expect(body.savedBinaries).toEqual([]);
    expect(typeof body.timestamp).toBe('string');
  });

  it('appends one line per call to logs/mcp-history.jsonl', async () => {
    saveMcpRaw({
      workspacePath: workspace,
      serverId: 'canva',
      toolName: 'tool_a',
      args: {},
      content: [{ type: 'text', text: 'A' }],
      isError: false,
      output: 'A',
      savedPaths: [],
    });
    saveMcpRaw({
      workspacePath: workspace,
      serverId: 'canva',
      toolName: 'tool_b',
      args: {},
      content: [{ type: 'text', text: 'B' }],
      isError: true,
      output: 'B',
      savedPaths: [],
    });

    const lines = (
      await fs.readFile(path.join(workspace, 'logs', 'mcp-history.jsonl'), 'utf-8')
    )
      .trim()
      .split('\n');
    expect(lines.length).toBe(2);
    const e0 = JSON.parse(lines[0]);
    const e1 = JSON.parse(lines[1]);
    expect(e0.toolName).toBe('tool_a');
    expect(e0.isError).toBe(false);
    expect(e0.serverId).toBe('canva');
    expect(e0.filename).toMatch(/^logs\/mcp\/canva\/tool_a-/);
    expect(e0.bytes).toBeGreaterThan(0);
    expect(e1.toolName).toBe('tool_b');
    expect(e1.isError).toBe(true);
  });

  it('redacts secrets in arguments', async () => {
    saveMcpRaw({
      workspacePath: workspace,
      serverId: 'oauth',
      toolName: 'tok',
      args: {
        prompt: 'hi',
        access_token: 'super-secret',
        nested: { refresh_token: 'r' },
      },
      content: [],
      isError: false,
      output: '',
      savedPaths: [],
    });
    const files = await listMcpDir('oauth');
    const body = JSON.parse(
      await fs.readFile(path.join(workspace, 'logs', 'mcp', 'oauth', files[0]), 'utf-8'),
    );
    expect(body.arguments.prompt).toBe('hi');
    expect(body.arguments.access_token).toBe('***');
    expect(body.arguments.nested.refresh_token).toBe('***');
  });

  it('strips base64 from image and resource blocks (replaces with reference)', async () => {
    saveMcpRaw({
      workspacePath: workspace,
      serverId: 'canva',
      toolName: 'render',
      args: {},
      content: [
        { type: 'image', data: 'AAAA'.repeat(10000), mimeType: 'image/png' },
        { type: 'resource', resource: { blob: 'BBBB'.repeat(10000), mimeType: 'application/pdf' } },
        { type: 'text', text: 'caption' },
      ],
      isError: false,
      output: 'Saved: output/mcp/canva/render-x.png',
      savedPaths: ['output/mcp/canva/render-x.png', 'output/mcp/canva/render-y.pdf'],
    });
    const files = await listMcpDir('canva');
    const body = JSON.parse(
      await fs.readFile(path.join(workspace, 'logs', 'mcp', 'canva', files[0]), 'utf-8'),
    );
    expect(body.content[0].type).toBe('image');
    expect(body.content[0].data).toMatch(/^<base64 omitted/);
    expect(body.content[0].mimeType).toBe('image/png');
    expect(body.content[1].resource.blob).toMatch(/^<base64 omitted/);
    expect(body.content[1].resource.mimeType).toBe('application/pdf');
    expect(body.content[2]).toEqual({ type: 'text', text: 'caption' });
    expect(body.savedBinaries).toEqual([
      'output/mcp/canva/render-x.png',
      'output/mcp/canva/render-y.pdf',
    ]);
  });

  it('saves failure responses as well (isError true with synthetic content)', async () => {
    saveMcpRaw({
      workspacePath: workspace,
      serverId: 'canva',
      toolName: 'fail',
      args: { x: 1 },
      content: [],
      isError: true,
      output: 'MCP call failed: boom',
      savedPaths: [],
    });
    const files = await listMcpDir('canva');
    expect(files.length).toBe(1);
    const body = JSON.parse(
      await fs.readFile(path.join(workspace, 'logs', 'mcp', 'canva', files[0]), 'utf-8'),
    );
    expect(body.isError).toBe(true);
    expect(body.output).toBe('MCP call failed: boom');
  });

  it('sanitizes unsafe serverId / toolName for filesystem paths', async () => {
    saveMcpRaw({
      workspacePath: workspace,
      serverId: '../escape',
      toolName: 'weird/tool name',
      args: {},
      content: [],
      isError: false,
      output: '',
      savedPaths: [],
    });
    // Must not create files outside workspace
    const outside = await fs.readdir(path.dirname(workspace));
    expect(outside.some((f) => f === 'escape')).toBe(false);
    // Should still write under logs/mcp/ with sanitized names
    const root = await fs.readdir(path.join(workspace, 'logs', 'mcp'));
    expect(root.length).toBe(1);
    expect(root[0]).not.toContain('/');
    expect(root[0]).not.toContain('..');
  });

  it('does not throw when workspacePath is invalid (best-effort)', () => {
    expect(() =>
      saveMcpRaw({
        workspacePath: '/nonexistent/path/that/should/fail/\0invalid',
        serverId: 'x',
        toolName: 'y',
        args: {},
        content: [],
        isError: false,
        output: '',
        savedPaths: [],
      }),
    ).not.toThrow();
  });
});
