/**
 * Tests for governance event capture hook.
 */

const assert = require('assert');

const {
  detectSecrets,
  detectApprovalRequired,
  detectSensitivePath,
  analyzeForGovernanceEvents,
  run,
} = require('../../scripts/hooks/governance-capture');

async function test(name, fn) {
  try {
    await fn();
    console.log(`  \u2713 ${name}`);
    return true;
  } catch (error) {
    console.log(`  \u2717 ${name}`);
    console.log(`    Error: ${error.message}`);
    return false;
  }
}

async function runTests() {
  console.log('\n=== Testing governance-capture ===\n');

  let passed = 0;
  let failed = 0;

  // ── detectSecrets ──────────────────────────────────────────

  if (await test('detectSecrets finds AWS access keys', async () => {
    const findings = detectSecrets('my key is AKIAIOSFODNN7EXAMPLE');
    assert.ok(findings.length > 0);
    assert.ok(findings.some(f => f.name === 'aws_key'));
  })) passed += 1; else failed += 1;

  if (await test('detectSecrets finds generic secrets', async () => {
    const findings = detectSecrets('api_key = "sk-proj-abcdefghij1234567890"');
    assert.ok(findings.length > 0);
    assert.ok(findings.some(f => f.name === 'generic_secret'));
  })) passed += 1; else failed += 1;

  if (await test('detectSecrets finds private keys', async () => {
    const findings = detectSecrets('-----BEGIN RSA PRIVATE KEY-----\nMIIE...');
    assert.ok(findings.length > 0);
    assert.ok(findings.some(f => f.name === 'private_key'));
  })) passed += 1; else failed += 1;

  if (await test('detectSecrets finds GitHub tokens', async () => {
    const findings = detectSecrets('token: ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij');
    assert.ok(findings.length > 0);
    assert.ok(findings.some(f => f.name === 'github_token'));
  })) passed += 1; else failed += 1;

  if (await test('detectSecrets returns empty array for clean text', async () => {
    const findings = detectSecrets('This is a normal log message with no secrets.');
    assert.strictEqual(findings.length, 0);
  })) passed += 1; else failed += 1;

  if (await test('detectSecrets handles null and undefined', async () => {
    assert.deepStrictEqual(detectSecrets(null), []);
    assert.deepStrictEqual(detectSecrets(undefined), []);
    assert.deepStrictEqual(detectSecrets(''), []);
  })) passed += 1; else failed += 1;

  // ── detectApprovalRequired ─────────────────────────────────

  if (await test('detectApprovalRequired flags force push', async () => {
    const findings = detectApprovalRequired('git push origin main --force');
    assert.ok(findings.length > 0);
  })) passed += 1; else failed += 1;

  if (await test('detectApprovalRequired flags hard reset', async () => {
    const findings = detectApprovalRequired('git reset --hard HEAD~3');
    assert.ok(findings.length > 0);
  })) passed += 1; else failed += 1;

  if (await test('detectApprovalRequired flags rm -rf', async () => {
    const findings = detectApprovalRequired('rm -rf /tmp/important');
    assert.ok(findings.length > 0);
  })) passed += 1; else failed += 1;

  if (await test('detectApprovalRequired flags DROP TABLE', async () => {
    const findings = detectApprovalRequired('DROP TABLE users');
    assert.ok(findings.length > 0);
  })) passed += 1; else failed += 1;

  if (await test('detectApprovalRequired allows safe commands', async () => {
    const findings = detectApprovalRequired('git status');
    assert.strictEqual(findings.length, 0);
  })) passed += 1; else failed += 1;

  if (await test('detectApprovalRequired handles null', async () => {
    assert.deepStrictEqual(detectApprovalRequired(null), []);
    assert.deepStrictEqual(detectApprovalRequired(''), []);
  })) passed += 1; else failed += 1;

  // ── detectSensitivePath ────────────────────────────────────

  if (await test('detectSensitivePath identifies .env files', async () => {
    assert.ok(detectSensitivePath('.env'));
    assert.ok(detectSensitivePath('.env.local'));
    assert.ok(detectSensitivePath('/project/.env.production'));
  })) passed += 1; else failed += 1;

  if (await test('detectSensitivePath identifies credential files', async () => {
    assert.ok(detectSensitivePath('credentials.json'));
    assert.ok(detectSensitivePath('/home/user/.ssh/id_rsa'));
    assert.ok(detectSensitivePath('server.key'));
    assert.ok(detectSensitivePath('cert.pem'));
  })) passed += 1; else failed += 1;

  if (await test('detectSensitivePath returns false for normal files', async () => {
    assert.ok(!detectSensitivePath('index.js'));
    assert.ok(!detectSensitivePath('README.md'));
    assert.ok(!detectSensitivePath('package.json'));
  })) passed += 1; else failed += 1;

  if (await test('detectSensitivePath handles null', async () => {
    assert.ok(!detectSensitivePath(null));
    assert.ok(!detectSensitivePath(''));
  })) passed += 1; else failed += 1;

  // ── analyzeForGovernanceEvents ─────────────────────────────

  if (await test('analyzeForGovernanceEvents detects secrets in tool input', async () => {
    const events = analyzeForGovernanceEvents({
      tool_name: 'Write',
      tool_input: {
        file_path: '/tmp/config.js',
        content: 'const key = "AKIAIOSFODNN7EXAMPLE";',
      },
    });

    assert.ok(events.length > 0);
    const secretEvent = events.find(e => e.eventType === 'secret_detected');
    assert.ok(secretEvent);
    assert.strictEqual(secretEvent.payload.severity, 'critical');
  })) passed += 1; else failed += 1;

  if (await test('analyzeForGovernanceEvents detects approval-required commands', async () => {
    const events = analyzeForGovernanceEvents({
      tool_name: 'Bash',
      tool_input: {
        command: 'git push origin main --force',
      },
    });

    assert.ok(events.length > 0);
    const approvalEvent = events.find(e => e.eventType === 'approval_requested');
    assert.ok(approvalEvent);
    assert.strictEqual(approvalEvent.payload.severity, 'high');
  })) passed += 1; else failed += 1;

  if (await test('approval events fingerprint commands instead of storing raw command text', async () => {
    const command = 'git push origin main --force';
    const events = analyzeForGovernanceEvents({
      tool_name: 'Bash',
      tool_input: { command },
    });

    const approvalEvent = events.find(e => e.eventType === 'approval_requested');
    assert.ok(approvalEvent);
    assert.strictEqual(approvalEvent.payload.commandName, 'git');
    assert.ok(/^[a-f0-9]{12}$/.test(approvalEvent.payload.commandFingerprint), 'Expected short command fingerprint');
    assert.ok(!Object.prototype.hasOwnProperty.call(approvalEvent.payload, 'command'), 'Should not store raw command text');
  })) passed += 1; else failed += 1;

  if (await test('security findings fingerprint elevated commands instead of storing raw command text', async () => {
    const command = 'sudo chmod 600 ~/.ssh/id_rsa';
    const events = analyzeForGovernanceEvents({
      tool_name: 'Bash',
      tool_input: { command },
    }, {
      hookPhase: 'post',
    });

    const securityEvent = events.find(e => e.eventType === 'security_finding');
    assert.ok(securityEvent);
    assert.strictEqual(securityEvent.payload.commandName, 'sudo');
    assert.ok(/^[a-f0-9]{12}$/.test(securityEvent.payload.commandFingerprint), 'Expected short command fingerprint');
    assert.ok(!Object.prototype.hasOwnProperty.call(securityEvent.payload, 'command'), 'Should not store raw command text');
  })) passed += 1; else failed += 1;
  if (await test('analyzeForGovernanceEvents detects sensitive file access', async () => {
    const events = analyzeForGovernanceEvents({
      tool_name: 'Edit',
      tool_input: {
        file_path: '/project/.env.production',
        old_string: 'DB_URL=old',
        new_string: 'DB_URL=new',
      },
    });

    assert.ok(events.length > 0);
    const policyEvent = events.find(e => e.eventType === 'policy_violation');
    assert.ok(policyEvent);
    assert.strictEqual(policyEvent.payload.reason, 'sensitive_file_access');
  })) passed += 1; else failed += 1;

  if (await test('analyzeForGovernanceEvents detects elevated privilege commands', async () => {
    const events = analyzeForGovernanceEvents({
      tool_name: 'Bash',
      tool_input: { command: 'sudo rm -rf /etc/something' },
    }, {
      hookPhase: 'post',
    });

    const securityEvent = events.find(e => e.eventType === 'security_finding');
    assert.ok(securityEvent);
    assert.strictEqual(securityEvent.payload.reason, 'elevated_privilege_command');
  })) passed += 1; else failed += 1;

  if (await test('analyzeForGovernanceEvents returns empty for clean inputs', async () => {
    const events = analyzeForGovernanceEvents({
      tool_name: 'Read',
      tool_input: { file_path: '/project/src/index.js' },
    });
    assert.strictEqual(events.length, 0);
  })) passed += 1; else failed += 1;

  if (await test('analyzeForGovernanceEvents populates session ID from context', async () => {
    const events = analyzeForGovernanceEvents({
      tool_name: 'Write',
      tool_input: {
        file_path: '/project/.env',
        content: 'DB_URL=test',
      },
    }, {
      sessionId: 'test-session-123',
    });

    assert.ok(events.length > 0);
    assert.strictEqual(events[0].sessionId, 'test-session-123');
  })) passed += 1; else failed += 1;

  if (await test('analyzeForGovernanceEvents generates unique event IDs', async () => {
    const events1 = analyzeForGovernanceEvents({
      tool_name: 'Write',
      tool_input: { file_path: '.env', content: '' },
    });
    const events2 = analyzeForGovernanceEvents({
      tool_name: 'Write',
      tool_input: { file_path: '.env.local', content: '' },
    });

    if (events1.length > 0 && events2.length > 0) {
      assert.notStrictEqual(events1[0].id, events2[0].id);
    }
  })) passed += 1; else failed += 1;

  // ── run() function ─────────────────────────────────────────

  if (await test('run() passes through input when feature flag is off', async () => {
    const original = process.env.ECC_GOVERNANCE_CAPTURE;
    delete process.env.ECC_GOVERNANCE_CAPTURE;

    try {
      const input = JSON.stringify({ tool_name: 'Bash', tool_input: { command: 'git push --force' } });
      const result = run(input);
      assert.strictEqual(result, input);
    } finally {
      if (original !== undefined) {
        process.env.ECC_GOVERNANCE_CAPTURE = original;
      }
    }
  })) passed += 1; else failed += 1;

  if (await test('run() passes through input when feature flag is on', async () => {
    const original = process.env.ECC_GOVERNANCE_CAPTURE;
    process.env.ECC_GOVERNANCE_CAPTURE = '1';

    try {
      const input = JSON.stringify({ tool_name: 'Read', tool_input: { file_path: 'index.js' } });
      const result = run(input);
      assert.strictEqual(result, input);
    } finally {
      if (original !== undefined) {
        process.env.ECC_GOVERNANCE_CAPTURE = original;
      } else {
        delete process.env.ECC_GOVERNANCE_CAPTURE;
      }
    }
  })) passed += 1; else failed += 1;

  if (await test('run() handles invalid JSON gracefully', async () => {
    const original = process.env.ECC_GOVERNANCE_CAPTURE;
    process.env.ECC_GOVERNANCE_CAPTURE = '1';

    try {
      const result = run('not valid json');
      assert.strictEqual(result, 'not valid json');
    } finally {
      if (original !== undefined) {
        process.env.ECC_GOVERNANCE_CAPTURE = original;
      } else {
        delete process.env.ECC_GOVERNANCE_CAPTURE;
      }
    }
  })) passed += 1; else failed += 1;

  if (await test('run() emits hook_input_truncated event without logging raw command text', async () => {
    const original = process.env.ECC_GOVERNANCE_CAPTURE;
    const originalHookEvent = process.env.CLAUDE_HOOK_EVENT_NAME;
    const originalWrite = process.stderr.write;
    const stderr = [];
    process.env.ECC_GOVERNANCE_CAPTURE = '1';
    process.env.CLAUDE_HOOK_EVENT_NAME = 'PreToolUse';
    process.stderr.write = (chunk, encoding, callback) => {
      stderr.push(String(chunk));
      if (typeof encoding === 'function') encoding();
      if (typeof callback === 'function') callback();
      return true;
    };

    try {
      const input = JSON.stringify({ tool_name: 'Bash', tool_input: { command: 'rm -rf /tmp/important' } });
      const result = run(input, { truncated: true, maxStdin: 1024 });
      assert.strictEqual(result, input);
    } finally {
      process.stderr.write = originalWrite;
      if (original !== undefined) {
        process.env.ECC_GOVERNANCE_CAPTURE = original;
      } else {
        delete process.env.ECC_GOVERNANCE_CAPTURE;
      }
      if (originalHookEvent !== undefined) {
        process.env.CLAUDE_HOOK_EVENT_NAME = originalHookEvent;
      } else {
        delete process.env.CLAUDE_HOOK_EVENT_NAME;
      }
    }

    const combined = stderr.join('');
    assert.ok(combined.includes('"eventType":"hook_input_truncated"'), 'Should emit truncation event');
    assert.ok(combined.includes('"sizeLimitBytes":1024'), 'Should record the truncation limit');
    assert.ok(!combined.includes('rm -rf /tmp/important'), 'Should not leak raw command text to governance logs');
  })) passed += 1; else failed += 1;
  if (await test('run() can detect multiple event types in one input', async () => {
    // Bash command with force push AND secret in command
    const events = analyzeForGovernanceEvents({
      tool_name: 'Bash',
      tool_input: {
        command: 'API_KEY="AKIAIOSFODNN7EXAMPLE" git push --force',
      },
    });

    const eventTypes = events.map(e => e.eventType);
    assert.ok(eventTypes.includes('secret_detected'));
    assert.ok(eventTypes.includes('approval_requested'));
  })) passed += 1; else failed += 1;

  console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
  process.exit(failed > 0 ? 1 : 0);
}

runTests();
