// mcp-baseline-reset.test.mjs — CLI tests for scanners/mcp-baseline-reset.mjs
// Zero external dependencies: node:test + node:assert + child_process.execFile.
//
// LLM_SECURITY_MCP_CACHE_FILE controls the cache path so the test does not
// pollute the user's real ~/.cache/llm-security/mcp-descriptions.json.

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
import { mkdtempSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';

const SCRIPT = resolve(import.meta.dirname, '../../scanners/mcp-baseline-reset.mjs');

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function runCli(args, env) {
  return new Promise((res) => {
    execFile(
      'node',
      [SCRIPT, ...args],
      { env: { ...process.env, ...env }, timeout: 5000 },
      (err, stdout, stderr) => {
        res({
          code: err && typeof err.code === 'number' ? err.code : 0,
          stdout: stdout || '',
          stderr: stderr || '',
        });
      },
    );
  });
}

function makeTmpCache() {
  const dir = mkdtempSync(join(tmpdir(), 'baseline-reset-test-'));
  const cacheFile = join(dir, 'mcp-descriptions.json');
  return { dir, cacheFile };
}

function cleanup(dir) {
  try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
}

function seedCache(cacheFile, entries) {
  writeFileSync(cacheFile, JSON.stringify(entries, null, 2), 'utf-8');
}

function parseJson(stdout) {
  return JSON.parse(stdout.trim());
}

const NOW = Date.now();

function makeEntry(desc, opts = {}) {
  return {
    description: desc,
    firstSeen: NOW - 10000,
    lastSeen: NOW,
    baseline: opts.noBaseline ? undefined : { description: desc, seenAt: NOW - 10000 },
    history: opts.history || [],
  };
}

// ---------------------------------------------------------------------------
// --list mode
// ---------------------------------------------------------------------------

describe('mcp-baseline-reset CLI — --list mode', () => {
  it('returns mode=list with empty baselines on empty cache', async () => {
    const { dir, cacheFile } = makeTmpCache();
    const result = await runCli(['--list'], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.mode, 'list');
    assert.equal(json.count, 0);
    assert.deepEqual(json.baselines, []);
    cleanup(dir);
  });

  it('lists all entries with baseline metadata', async () => {
    const { dir, cacheFile } = makeTmpCache();
    seedCache(cacheFile, {
      'mcp__alpha__t': makeEntry('Alpha description text long enough'),
      'mcp__beta__t': makeEntry('Beta description text long enough'),
    });
    const result = await runCli(['--list'], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.mode, 'list');
    assert.equal(json.count, 2);
    const tools = json.baselines.map((b) => b.tool).sort();
    assert.deepEqual(tools, ['mcp__alpha__t', 'mcp__beta__t']);
    for (const b of json.baselines) {
      assert.ok(typeof b.baseline_excerpt === 'string');
      assert.ok(typeof b.seen_at === 'number');
      assert.ok(typeof b.last_seen === 'number');
      assert.ok(typeof b.history_events === 'number');
    }
    cleanup(dir);
  });

  it('--list does not mutate the cache', async () => {
    const { dir, cacheFile } = makeTmpCache();
    const before = {
      'mcp__alpha__t': makeEntry('Alpha description text long enough'),
    };
    seedCache(cacheFile, before);
    await runCli(['--list'], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    const after = JSON.parse(readFileSync(cacheFile, 'utf-8'));
    assert.ok(after['mcp__alpha__t'].baseline, 'baseline preserved by --list');
    cleanup(dir);
  });
});

// ---------------------------------------------------------------------------
// --target mode (single tool)
// ---------------------------------------------------------------------------

describe('mcp-baseline-reset CLI — --target mode', () => {
  it('clears one named baseline and reports it', async () => {
    const { dir, cacheFile } = makeTmpCache();
    seedCache(cacheFile, {
      'mcp__alpha__t': makeEntry('Alpha description text long enough'),
      'mcp__beta__t': makeEntry('Beta description text long enough'),
    });
    const result = await runCli(
      ['--target', 'mcp__alpha__t'],
      { LLM_SECURITY_MCP_CACHE_FILE: cacheFile },
    );
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.mode, 'reset');
    assert.equal(json.cleared, 1);
    assert.deepEqual(json.tools, ['mcp__alpha__t']);
    assert.equal(json.remaining, 1, 'beta baseline still present');

    // Verify on disk
    const after = JSON.parse(readFileSync(cacheFile, 'utf-8'));
    assert.equal(after['mcp__alpha__t'].baseline, undefined, 'alpha baseline cleared');
    assert.ok(after['mcp__beta__t'].baseline, 'beta baseline preserved');
    cleanup(dir);
  });

  it('idempotent — clearing nonexistent target reports 0 cleared', async () => {
    const { dir, cacheFile } = makeTmpCache();
    seedCache(cacheFile, {
      'mcp__alpha__t': makeEntry('Alpha description text long enough'),
    });
    const result = await runCli(
      ['--target', 'mcp__no_such__tool'],
      { LLM_SECURITY_MCP_CACHE_FILE: cacheFile },
    );
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.cleared, 0);
    assert.deepEqual(json.tools, []);
    assert.equal(json.remaining, 1, 'unrelated baseline untouched');
    cleanup(dir);
  });
});

// ---------------------------------------------------------------------------
// Clear-all mode (no args)
// ---------------------------------------------------------------------------

describe('mcp-baseline-reset CLI — clear-all mode', () => {
  it('with no args, clears all baselines', async () => {
    const { dir, cacheFile } = makeTmpCache();
    seedCache(cacheFile, {
      'mcp__alpha__t': makeEntry('Alpha description text long enough'),
      'mcp__beta__t': makeEntry('Beta description text long enough'),
      'mcp__gamma__t': makeEntry('Gamma description text long enough'),
    });
    const result = await runCli([], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.mode, 'reset');
    assert.equal(json.cleared, 3);
    assert.equal(json.remaining, 0);
    assert.equal(json.tools.length, 3);

    const after = JSON.parse(readFileSync(cacheFile, 'utf-8'));
    for (const key of ['mcp__alpha__t', 'mcp__beta__t', 'mcp__gamma__t']) {
      assert.equal(after[key].baseline, undefined);
    }
    cleanup(dir);
  });

  it('idempotent — clear-all on empty cache returns 0', async () => {
    const { dir, cacheFile } = makeTmpCache();
    const result = await runCli([], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.cleared, 0);
    assert.equal(json.remaining, 0);
    cleanup(dir);
  });

  it('preserves description and history after clear', async () => {
    const { dir, cacheFile } = makeTmpCache();
    seedCache(cacheFile, {
      'mcp__alpha__t': makeEntry('Alpha description text long enough', {
        history: [{ description: 'older', seenAt: NOW - 5000, distance: 4 }],
      }),
    });
    await runCli([], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    const after = JSON.parse(readFileSync(cacheFile, 'utf-8'));
    const entry = after['mcp__alpha__t'];
    assert.equal(entry.baseline, undefined);
    assert.equal(entry.description, 'Alpha description text long enough', 'description preserved');
    assert.ok(typeof entry.firstSeen === 'number');
    assert.equal(entry.history.length, 1, 'history preserved');
    cleanup(dir);
  });
});

// ---------------------------------------------------------------------------
// Help / unknown args
// ---------------------------------------------------------------------------

describe('mcp-baseline-reset CLI — misc', () => {
  it('--help prints usage and exits 0', async () => {
    const result = await runCli(['--help'], {});
    assert.equal(result.code, 0);
    assert.ok(/Usage:/i.test(result.stdout));
  });

  it('bare positional argument is treated as --target', async () => {
    const { dir, cacheFile } = makeTmpCache();
    seedCache(cacheFile, {
      'mcp__alpha__t': makeEntry('Alpha description text long enough'),
      'mcp__beta__t': makeEntry('Beta description text long enough'),
    });
    const result = await runCli(['mcp__alpha__t'], { LLM_SECURITY_MCP_CACHE_FILE: cacheFile });
    assert.equal(result.code, 0);
    const json = parseJson(result.stdout);
    assert.equal(json.cleared, 1);
    assert.deepEqual(json.tools, ['mcp__alpha__t']);
    cleanup(dir);
  });
});
