// network.test.mjs — Integration tests for the network-mapper
// Tests against the evil-project-health fixture which contains URLs to:
//   - ngrok-free.app (SUSPICIOUS_DOMAINS — HIGH)
//   - webhook.site   (SUSPICIOUS_DOMAINS — HIGH)
//   - requestbin.com (SUSPICIOUS_DOMAINS — HIGH)
//   - pipedream.net  (SUSPICIOUS_DOMAINS — HIGH)
//   - pastebin.com   (SUSPICIOUS_DOMAINS — HIGH)
//   - bit.ly         (SUSPICIOUS_DOMAINS — HIGH, URL shortener)
//   - 192.168.x.x    (private IP — MEDIUM)
//   - 45.33.32.156   (public IP — HIGH, bypasses DNS)
//
// We do NOT assert on DNS resolution — it is network-dependent.
// Only URL pattern detection (Phase 1–2 of the scanner) is tested.

import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { resetCounter } from '../../scanners/lib/output.mjs';
import { discoverFiles } from '../../scanners/lib/file-discovery.mjs';
import { scan } from '../../scanners/network-mapper.mjs';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
const FIXTURE = resolve(__dirname, '../../examples/malicious-skill-demo/evil-project-health');

describe('network-mapper integration', () => {
  let discovery;

  beforeEach(async () => {
    resetCounter();
    discovery = await discoverFiles(FIXTURE);
  });

  it('returns status ok', async () => {
    const result = await scan(FIXTURE, discovery);
    assert.equal(result.status, 'ok', `Expected status 'ok', got '${result.status}'`);
  });

  it('scans at least one file', async () => {
    const result = await scan(FIXTURE, discovery);
    assert.ok(result.files_scanned >= 1, `Expected files_scanned >= 1, got ${result.files_scanned}`);
  });

  it('detects at least 2 suspicious domain findings', async () => {
    const result = await scan(FIXTURE, discovery);
    const suspiciousFindings = result.findings.filter(f => f.severity === 'high');
    assert.ok(
      suspiciousFindings.length >= 2,
      `Expected >= 2 HIGH severity network findings, got ${suspiciousFindings.length}. ` +
      `All findings: ${result.findings.map(f => `${f.severity}: ${f.title}`).join('; ')}`
    );
  });

  it('reports total findings >= 2', async () => {
    const result = await scan(FIXTURE, discovery);
    assert.ok(
      result.findings.length >= 2,
      `Expected >= 2 network findings, got ${result.findings.length}`
    );
  });

  it('detects ngrok-free.app as suspicious endpoint', async () => {
    const result = await scan(FIXTURE, discovery);
    const ngrokFinding = result.findings.find(
      f => f.title.toLowerCase().includes('ngrok') ||
           (f.evidence && f.evidence.toLowerCase().includes('ngrok'))
    );
    assert.ok(
      ngrokFinding,
      `Should detect ngrok-free.app. All titles: ${result.findings.map(f => f.title).join('; ')}`
    );
  });

  it('detects webhook.site as suspicious endpoint', async () => {
    const result = await scan(FIXTURE, discovery);
    const webhookFinding = result.findings.find(
      f => f.title.toLowerCase().includes('webhook.site') ||
           (f.evidence && f.evidence.toLowerCase().includes('webhook.site'))
    );
    assert.ok(
      webhookFinding,
      `Should detect webhook.site. All titles: ${result.findings.map(f => f.title).join('; ')}`
    );
  });

  it('suspicious domain findings reference owasp LLM02', async () => {
    const result = await scan(FIXTURE, discovery);
    const suspiciousFindings = result.findings.filter(
      f => f.severity === 'high' && f.title.toLowerCase().includes('suspicious')
    );
    for (const f of suspiciousFindings) {
      assert.equal(
        f.owasp, 'LLM02',
        `Suspicious domain finding ${f.id} should be LLM02, got ${f.owasp}`
      );
    }
  });

  it('all findings have DS-NET- prefix', async () => {
    const result = await scan(FIXTURE, discovery);
    const wrongPrefix = result.findings.filter(f => !f.id.startsWith('DS-NET-'));
    assert.equal(
      wrongPrefix.length, 0,
      `All network findings should have DS-NET- prefix. Wrong: ${wrongPrefix.map(f => f.id).join(', ')}`
    );
  });

  it('finding IDs start from DS-NET-001 after reset', async () => {
    const result = await scan(FIXTURE, discovery);
    if (result.findings.length === 0) return;
    assert.equal(result.findings[0].id, 'DS-NET-001');
  });

  it('counts total matches findings array length', async () => {
    const result = await scan(FIXTURE, discovery);
    const countTotal = Object.values(result.counts).reduce((s, n) => s + n, 0);
    assert.equal(
      countTotal, result.findings.length,
      `counts total (${countTotal}) should match findings.length (${result.findings.length})`
    );
  });

  it('does not emit findings for trusted domains (github.com, anthropic.com)', async () => {
    const result = await scan(FIXTURE, discovery);
    const trustedDomainFindings = result.findings.filter(
      f => (f.evidence && (
        f.evidence.includes('github.com') ||
        f.evidence.includes('anthropic.com') ||
        f.evidence.includes('npmjs.org')
      ))
    );
    assert.equal(
      trustedDomainFindings.length, 0,
      `Should not flag trusted domains. Found: ${trustedDomainFindings.map(f => f.evidence).join(', ')}`
    );
  });
});
