import assert from 'node:assert/strict';
import test from 'node:test';

import {
  buildConsolidatedAdvisoryFeed,
  buildGhsaWithoutCveFeed,
  fetchGitHubSecurityAdvisories,
  inferPlatforms,
  normalizeGhsaAdvisory,
} from './ghsa-without-cve-feed.mjs';

const fixedNow = '2026-05-24T00:00:00Z';

function advisory(overrides = {}) {
  return {
    ghsa_id: 'GHSA-test-1111-2222',
    cve_id: null,
    html_url: 'https://github.com/openclaw/openclaw/security/advisories/GHSA-test-1111-2222',
    summary: 'Workspace bridge allows sandbox escape',
    description: 'OpenClaw before 2026.4.25 allowed a sandbox escape.',
    severity: 'high',
    published_at: '2026-04-24T00:00:00Z',
    updated_at: '2026-04-25T00:00:00Z',
    vulnerabilities: [
      {
        package: { ecosystem: 'npm', name: 'openclaw' },
        vulnerable_version_range: '<2026.4.25',
        patched_versions: '2026.4.25',
      },
    ],
    cvss: {
      vector_string: 'CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H',
      score: 7.8,
    },
    cwe_ids: ['CWE-94'],
    credits: [{ login: 'researcher', type: 'reporter' }],
    ...overrides,
  };
}

test('inferPlatforms maps known repositories to feed platforms', () => {
  assert.deepEqual(inferPlatforms('openclaw/openclaw'), ['openclaw']);
  assert.deepEqual(inferPlatforms('qwibitai/nanoclaw'), ['nanoclaw']);
  assert.deepEqual(inferPlatforms('softwarepub/hermes'), ['hermes']);
  assert.deepEqual(inferPlatforms('sipeed/picoclaw'), ['picoclaw']);
});

test('fetchGitHubSecurityAdvisories follows cursor pagination links', async (t) => {
  const originalFetch = globalThis.fetch;
  const nextUrl =
    'https://api.github.com/repositories/1103012935/security-advisories?per_page=100&after=cursor';
  const calls = [];

  globalThis.fetch = async (url) => {
    calls.push(String(url));
    if (calls.length === 1) {
      return new globalThis.Response(
        JSON.stringify(
          Array.from({ length: 100 }, (_, index) =>
            advisory({ ghsa_id: `GHSA-page-1111-${String(index).padStart(4, '0')}` }),
          ),
        ),
        {
          status: 200,
          headers: {
            Link: `<${nextUrl}>; rel="next"`,
          },
        },
      );
    }
    if (String(url) !== nextUrl) {
      throw new Error(`unexpected pagination URL: ${url}`);
    }
    return new globalThis.Response(JSON.stringify([advisory({ ghsa_id: 'GHSA-next-1111-2222' })]), {
      status: 200,
    });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const advisories = await fetchGitHubSecurityAdvisories('openclaw/openclaw', {
    token: 'test-token',
  });

  assert.equal(calls.length, 2);
  assert.equal(calls[1], nextUrl);
  assert.equal(advisories.length, 101);
  assert.equal(advisories.at(-1).ghsa_id, 'GHSA-next-1111-2222');
});

test('normalizeGhsaAdvisory marks fresh GHSA-only advisories active', () => {
  const normalized = normalizeGhsaAdvisory(advisory(), {
    now: fixedNow,
    repository: 'openclaw/openclaw',
    staleAfterDays: 60,
  });

  assert.equal(normalized.id, 'GHSA-test-1111-2222');
  assert.equal(normalized.status, 'active');
  assert.equal(normalized.cve_id, null);
  assert.equal(normalized.stale, false);
  assert.deepEqual(normalized.platforms, ['openclaw']);
  assert.deepEqual(normalized.affected, ['openclaw@<2026.4.25']);
});

test('normalizeGhsaAdvisory marks old GHSA-only advisories stale after threshold', () => {
  const normalized = normalizeGhsaAdvisory(
    advisory({ published_at: '2026-03-01T00:00:00Z' }),
    {
      now: fixedNow,
      repository: 'openclaw/openclaw',
      staleAfterDays: 60,
    },
  );

  assert.equal(normalized.status, 'stale');
  assert.equal(normalized.stale, true);
  assert.equal(normalized.cve_id, null);
});

test('normalizeGhsaAdvisory marks existing GHSA entries matured when a CVE appears', () => {
  const normalized = normalizeGhsaAdvisory(
    advisory({ cve_id: 'CVE-2026-9999' }),
    {
      now: fixedNow,
      repository: 'openclaw/openclaw',
      staleAfterDays: 60,
    },
  );

  assert.equal(normalized.status, 'matured');
  assert.equal(normalized.stale, false);
  assert.equal(normalized.cve_id, 'CVE-2026-9999');
  assert.equal(normalized.nvd_url, 'https://nvd.nist.gov/vuln/detail/CVE-2026-9999');
});

test('buildGhsaWithoutCveFeed only imports CVE-backed advisories that were already tracked', () => {
  const existing = {
    version: '0.1.0',
    advisories: [
      normalizeGhsaAdvisory(advisory({ ghsa_id: 'GHSA-old-1111-2222' }), {
        now: '2026-04-25T00:00:00Z',
        repository: 'openclaw/openclaw',
        staleAfterDays: 60,
      }),
    ],
  };
  const fetched = [
    {
      repository: 'openclaw/openclaw',
      advisories: [
        advisory({ ghsa_id: 'GHSA-new-1111-2222', cve_id: null }),
        advisory({ ghsa_id: 'GHSA-old-1111-2222', cve_id: 'CVE-2026-1111' }),
        advisory({ ghsa_id: 'GHSA-cve-only-1111-2222', cve_id: 'CVE-2026-2222' }),
      ],
    },
  ];

  const feed = buildGhsaWithoutCveFeed({
    fetched,
    existingFeed: existing,
    nvdFeed: { advisories: [] },
    now: fixedNow,
    staleAfterDays: 60,
  });

  assert.deepEqual(
    feed.advisories.map((entry) => [entry.id, entry.status, entry.cve_id]),
    [
      ['GHSA-new-1111-2222', 'active', null],
      ['GHSA-old-1111-2222', 'matured', 'CVE-2026-1111'],
    ],
  );
});

test('buildGhsaWithoutCveFeed matures tracked GHSAs when the CVE feed references them', () => {
  const existing = {
    version: '0.1.0',
    advisories: [
      normalizeGhsaAdvisory(advisory({ ghsa_id: 'GHSA-oooo-3333-4444' }), {
        now: '2026-04-25T00:00:00Z',
        repository: 'openclaw/openclaw',
        staleAfterDays: 60,
      }),
    ],
  };
  const feed = buildGhsaWithoutCveFeed({
    fetched: [
      {
        repository: 'openclaw/openclaw',
        advisories: [advisory({ ghsa_id: 'GHSA-oooo-3333-4444', cve_id: null })],
      },
    ],
    existingFeed: existing,
    nvdFeed: {
      advisories: [
        {
          id: 'CVE-2026-3333',
          references: [
            'https://github.com/openclaw/openclaw/security/advisories/GHSA-oooo-3333-4444',
          ],
        },
      ],
    },
    now: fixedNow,
    staleAfterDays: 60,
  });

  assert.equal(feed.advisories[0].status, 'matured');
  assert.equal(feed.advisories[0].cve_id, 'CVE-2026-3333');
});

test('buildConsolidatedAdvisoryFeed appends active GHSA advisories without moving the NVD poll cursor', () => {
  const canonicalFeed = {
    version: '1.0.0',
    updated: '2026-05-23T00:00:00Z',
    description: 'Community-driven security advisory feed for ClawSec',
    advisories: [
      {
        id: 'CVE-2026-1111',
        severity: 'high',
        type: 'os_command_injection',
        title: 'Existing CVE',
        description: 'Existing CVE advisory',
        affected: ['openclaw@*'],
        platforms: ['openclaw'],
        action: 'Review NVD.',
        published: '2026-05-01T00:00:00Z',
      },
    ],
  };
  const ghsaFeed = {
    advisories: [
      normalizeGhsaAdvisory(advisory({ ghsa_id: 'GHSA-active-1111-2222', cve_id: null }), {
        now: fixedNow,
        repository: 'openclaw/openclaw',
        staleAfterDays: 60,
      }),
    ],
  };

  const consolidated = buildConsolidatedAdvisoryFeed({
    canonicalFeed,
    ghsaFeed,
    now: fixedNow,
  });

  assert.deepEqual(
    consolidated.advisories.map((entry) => entry.id),
    ['CVE-2026-1111', 'GHSA-active-1111-2222'],
  );
  assert.equal(consolidated.updated, canonicalFeed.updated);
  assert.equal(consolidated.advisories[1].source_feed, 'ghsa-without-cve');
});

test('buildConsolidatedAdvisoryFeed keeps existing GHSA advisories when replacement feed is empty', () => {
  const canonicalFeed = {
    version: '1.0.0',
    updated: '2026-05-23T00:00:00Z',
    advisories: [
      {
        id: 'CVE-2026-1111',
        published: '2026-05-01T00:00:00Z',
      },
      {
        id: 'GHSA-keep-1111-2222',
        ghsa_id: 'GHSA-keep-1111-2222',
        status: 'active',
        published: '2026-05-02T00:00:00Z',
        source_feed: 'ghsa-without-cve',
      },
    ],
  };

  const consolidated = buildConsolidatedAdvisoryFeed({
    canonicalFeed,
    ghsaFeed: { advisories: [] },
    now: fixedNow,
  });

  assert.deepEqual(
    consolidated.advisories.map((entry) => entry.id),
    ['GHSA-keep-1111-2222', 'CVE-2026-1111'],
  );
});

test('buildConsolidatedAdvisoryFeed replaces only matching GHSA canonical entries', () => {
  const canonicalFeed = {
    version: '1.0.0',
    updated: '2026-05-23T00:00:00Z',
    advisories: [
      {
        id: 'GHSA-repl-1111-2222',
        ghsa_id: 'GHSA-repl-1111-2222',
        status: 'active',
        title: 'Old GHSA payload',
        published: '2026-05-01T00:00:00Z',
        source_feed: 'ghsa-without-cve',
      },
      {
        id: 'GHSA-keep-3333-4444',
        ghsa_id: 'GHSA-keep-3333-4444',
        status: 'active',
        title: 'Retained GHSA payload',
        published: '2026-05-02T00:00:00Z',
        source_feed: 'ghsa-without-cve',
      },
    ],
  };
  const ghsaFeed = {
    advisories: [
      {
        id: 'GHSA-repl-1111-2222',
        ghsa_id: 'GHSA-repl-1111-2222',
        status: 'stale',
        title: 'Replacement GHSA payload',
        published: '2026-05-03T00:00:00Z',
      },
    ],
  };

  const consolidated = buildConsolidatedAdvisoryFeed({
    canonicalFeed,
    ghsaFeed,
    now: fixedNow,
  });

  assert.deepEqual(
    consolidated.advisories.map((entry) => [entry.id, entry.title, entry.status]),
    [
      ['GHSA-repl-1111-2222', 'Replacement GHSA payload', 'stale'],
      ['GHSA-keep-3333-4444', 'Retained GHSA payload', 'active'],
    ],
  );
});

test('buildConsolidatedAdvisoryFeed drops GHSA duplicate when matching CVE is present', () => {
  const canonicalFeed = {
    version: '1.0.0',
    updated: '2026-05-23T00:00:00Z',
    advisories: [
      {
        id: 'CVE-2026-2222',
        severity: 'high',
        type: 'code_injection',
        title: 'Canonical CVE',
        description: 'Canonical CVE advisory',
        affected: ['openclaw@*'],
        platforms: ['openclaw'],
        action: 'Review NVD.',
        published: '2026-05-02T00:00:00Z',
      },
      {
        id: 'GHSA-old-duplicate',
        ghsa_id: 'GHSA-old-duplicate',
        cve_id: 'CVE-2026-2222',
        status: 'matured',
        source_feed: 'ghsa-without-cve',
        severity: 'high',
        type: 'github_security_advisory',
        title: 'Old duplicate',
        description: 'Old provisional duplicate',
        affected: ['openclaw@*'],
        platforms: ['openclaw'],
        action: 'Track CVE.',
        published: '2026-05-01T00:00:00Z',
      },
    ],
  };
  const ghsaFeed = {
    advisories: [
      normalizeGhsaAdvisory(
        advisory({ ghsa_id: 'GHSA-new-duplicate', cve_id: 'CVE-2026-2222' }),
        {
          now: fixedNow,
          repository: 'openclaw/openclaw',
          staleAfterDays: 60,
        },
      ),
    ],
  };

  const consolidated = buildConsolidatedAdvisoryFeed({
    canonicalFeed,
    ghsaFeed,
    now: fixedNow,
  });

  assert.deepEqual(
    consolidated.advisories.map((entry) => entry.id),
    ['CVE-2026-2222'],
  );
});

test('buildConsolidatedAdvisoryFeed keeps matured GHSA until CVE lands in canonical feed', () => {
  const canonicalFeed = {
    version: '1.0.0',
    updated: '2026-05-23T00:00:00Z',
    advisories: [],
  };
  const ghsaFeed = {
    advisories: [
      normalizeGhsaAdvisory(
        advisory({ ghsa_id: 'GHSA-matured-1111-2222', cve_id: 'CVE-2026-4444' }),
        {
          now: fixedNow,
          repository: 'openclaw/openclaw',
          staleAfterDays: 60,
        },
      ),
    ],
  };

  const consolidated = buildConsolidatedAdvisoryFeed({
    canonicalFeed,
    ghsaFeed,
    now: fixedNow,
  });

  assert.deepEqual(
    consolidated.advisories.map((entry) => [entry.id, entry.status, entry.cve_id]),
    [['GHSA-matured-1111-2222', 'matured', 'CVE-2026-4444']],
  );
});
