'use strict';

/**
 * Integration tests for v1.4 MCP tools: explain_file and list_modules.
 */

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');

const GEN_CONTEXT = path.resolve(__dirname, '../../../gen-context.js');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log(`  PASS  ${name}`);
    passed++;
  } catch (err) {
    console.log(`  FAIL  ${name}: ${err.message}`);
    failed++;
  }
}

function mcpCall(messages, cwd) {
  const input = (Array.isArray(messages) ? messages : [messages])
    .map((m) => JSON.stringify(m)).join('\n') + '\n';
  const stdout = execSync(`node "${GEN_CONTEXT}" --mcp`, {
    input, cwd, encoding: 'utf8', timeout: 10000,
  });
  return stdout.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
}

function withTempProject(fn) {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cf-mcp14-'));
  try {
    fn(dir);
  } finally {
    fs.rmSync(dir, { recursive: true, force: true });
  }
}

function seedContextFile(dir) {
  const ghDir = path.join(dir, '.github');
  fs.mkdirSync(ghDir, { recursive: true });
  fs.writeFileSync(
    path.join(ghDir, 'copilot-instructions.md'),
    [
      '<!-- Generated by SigMap -->',
      '# Code signatures',
      '',
      '## src',
      '',
      '### src/auth/login.js',
      '```',
      'function login(user, pass)',
      'function logout(session)',
      '```',
      '',
      '### src/api/routes.js',
      '```',
      'function getUser(req, res)',
      'function createUser(req, res)',
      '```',
      '',
      '### src/utils/hash.js',
      '```',
      'function hashPassword(plain)',
      'function compareHash(plain, hash)',
      '```',
      '',
    ].join('\n')
  );
}

// ─────────────────────────────────────────────────────────────
// Gate: tools/list now returns 12 tools including get_callee_signatures
// ─────────────────────────────────────────────────────────────

console.log('\nMCP v1.4 — tools/list\n');

test('tools/list returns exactly 18 tools', () => {
  withTempProject((dir) => {
    const [res] = mcpCall({ jsonrpc: '2.0', method: 'tools/list', id: 1 }, dir);
    assert.ok(res.result, 'Should have result');
    assert.ok(Array.isArray(res.result.tools), 'tools should be array');
    assert.strictEqual(res.result.tools.length, 18, `Expected 18 tools, got ${res.result.tools.length}`);
    const names = res.result.tools.map((t) => t.name);
    assert.ok(names.includes('explain_file'), 'Should have explain_file');
    assert.ok(names.includes('list_modules'), 'Should have list_modules');
    assert.ok(names.includes('query_context'), 'Should have query_context');
    assert.ok(names.includes('read_memory'), 'Should have read_memory');
  });
});

// ─────────────────────────────────────────────────────────────
// explain_file — happy path
// ─────────────────────────────────────────────────────────────

console.log('\nMCP v1.4 — explain_file\n');

test('explain_file returns signatures for a known file', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 2, params: { name: 'explain_file', arguments: { path: 'src/auth/login.js' } } },
      dir
    );
    assert.ok(res.result, 'Should have result');
    const text = res.result.content[0].text;
    assert.ok(text.includes('login'), 'Should include login signature');
    assert.ok(text.includes('logout'), 'Should include logout signature');
  });
});

test('explain_file includes Signatures section header', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 3, params: { name: 'explain_file', arguments: { path: 'src/api/routes.js' } } },
      dir
    );
    const text = res.result.content[0].text;
    assert.ok(text.includes('Signatures') || text.includes('signatures') || text.includes('getUser'), 'Should include signature content');
  });
});

test('explain_file includes Imports section when file exists on disk', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    // Write the actual file so explain_file can read its imports
    const authDir = path.join(dir, 'src', 'auth');
    fs.mkdirSync(authDir, { recursive: true });
    fs.writeFileSync(path.join(authDir, 'login.js'), 'function login(user, pass) {}\nfunction logout(session) {}\nmodule.exports = { login, logout };\n', 'utf8');
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 4, params: { name: 'explain_file', arguments: { path: 'src/auth/login.js' } } },
      dir
    );
    const text = res.result.content[0].text;
    // File is on disk — should include the Imports section
    assert.ok(text.toLowerCase().includes('import'), 'Should include imports section');
  });
});

test('explain_file includes Callers section when file exists on disk', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const authDir = path.join(dir, 'src', 'auth');
    fs.mkdirSync(authDir, { recursive: true });
    fs.writeFileSync(path.join(authDir, 'login.js'), 'function login(user, pass) {}\nfunction logout(session) {}\nmodule.exports = { login, logout };\n', 'utf8');
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 5, params: { name: 'explain_file', arguments: { path: 'src/auth/login.js' } } },
      dir
    );
    const text = res.result.content[0].text;
    assert.ok(text.toLowerCase().includes('caller'), 'Should include callers section');
  });
});

// ─────────────────────────────────────────────────────────────
// explain_file — error cases
// ─────────────────────────────────────────────────────────────

test('explain_file returns graceful error for unknown path', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 6, params: { name: 'explain_file', arguments: { path: 'src/nonexistent/file.js' } } },
      dir
    );
    assert.ok(res.result, 'Should have result (not a JSON-RPC error)');
    const text = res.result.content[0].text;
    assert.ok(text.length > 0, 'Should return non-empty message');
    // Should not throw or return JSON-RPC error
    assert.ok(!res.error, 'Should not be a protocol-level error');
  });
});

test('explain_file returns error when path arg missing', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 7, params: { name: 'explain_file', arguments: {} } },
      dir
    );
    assert.ok(res.result, 'Should have result');
    const text = res.result.content[0].text;
    assert.ok(text.length > 0, 'Should return non-empty message');
  });
});

test('explain_file returns error when no context file exists', () => {
  withTempProject((dir) => {
    // No seedContextFile
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 8, params: { name: 'explain_file', arguments: { path: 'src/auth.js' } } },
      dir
    );
    assert.ok(res.result, 'Should have result');
    const text = res.result.content[0].text;
    assert.ok(text.length > 0, 'Should return non-empty message');
    assert.ok(!res.error, 'Should not be a protocol-level error');
  });
});

// ─────────────────────────────────────────────────────────────
// list_modules — happy path
// ─────────────────────────────────────────────────────────────

console.log('\nMCP v1.4 — list_modules\n');

test('list_modules returns a module listing table', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 9, params: { name: 'list_modules', arguments: {} } },
      dir
    );
    assert.ok(res.result, 'Should have result');
    const text = res.result.content[0].text;
    assert.ok(text.length > 0, 'Should return non-empty content');
    // The table should list the top-level "src" directory
    assert.ok(text.includes('src'), 'Should list src as a module');
  });
});

test('list_modules includes token count info', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 10, params: { name: 'list_modules', arguments: {} } },
      dir
    );
    const text = res.result.content[0].text;
    // Should include token column or count
    assert.ok(text.match(/\d+/) , 'Should include numeric token counts');
  });
});

test('list_modules includes file count column', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 11, params: { name: 'list_modules', arguments: {} } },
      dir
    );
    const text = res.result.content[0].text;
    // Should mention files
    assert.ok(text.toLowerCase().includes('file') || text.includes('|'), 'Should have table structure with file info');
  });
});

// ─────────────────────────────────────────────────────────────
// list_modules — error case
// ─────────────────────────────────────────────────────────────

test('list_modules returns error when no context file exists', () => {
  withTempProject((dir) => {
    // No seedContextFile
    const [res] = mcpCall(
      { jsonrpc: '2.0', method: 'tools/call', id: 12, params: { name: 'list_modules', arguments: {} } },
      dir
    );
    assert.ok(res.result, 'Should have result');
    const text = res.result.content[0].text;
    assert.ok(text.length > 0, 'Should return non-empty message');
    assert.ok(!res.error, 'Should not be a protocol-level error');
  });
});

// ─────────────────────────────────────────────────────────────
// Multiple v1.4 tools in one session
// ─────────────────────────────────────────────────────────────

console.log('\nMCP v1.4 — multi-call session\n');

test('explain_file and list_modules work in the same session', () => {
  withTempProject((dir) => {
    seedContextFile(dir);
    const responses = mcpCall(
      [
        { jsonrpc: '2.0', method: 'tools/call', id: 1, params: { name: 'list_modules', arguments: {} } },
        { jsonrpc: '2.0', method: 'tools/call', id: 2, params: { name: 'explain_file', arguments: { path: 'src/api/routes.js' } } },
      ],
      dir
    );
    assert.strictEqual(responses.length, 2, 'Should get 2 responses');
    assert.strictEqual(responses[0].id, 1);
    assert.strictEqual(responses[1].id, 2);
    const modulesText = responses[0].result.content[0].text;
    const explainText = responses[1].result.content[0].text;
    assert.ok(modulesText.includes('src'), 'list_modules should list src');
    assert.ok(explainText.includes('getUser') || explainText.includes('routes'), 'explain_file should include routes signatures');
  });
});

// ─────────────────────────────────────────────────────────────
// Summary
// ─────────────────────────────────────────────────────────────

console.log(`\n${'─'.repeat(50)}`);
console.log(`mcp-v14: ${passed} passed, ${failed} failed`);
if (failed > 0) process.exit(1);
