// ai-bom.test.mjs — Tests for AI-BOM generator (CycloneDX 1.6)

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { discoverComponents, buildAIBOM } from '../../scanners/lib/bom-builder.mjs';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
const GRADE_A_FIXTURE = resolve(__dirname, '../fixtures/posture-scan/grade-a-project');
const GRADE_F_FIXTURE = resolve(__dirname, '../fixtures/posture-scan/grade-f-project');
const PLUGIN_ROOT = resolve(__dirname, '../..');

// ---------------------------------------------------------------------------
// CycloneDX structure tests
// ---------------------------------------------------------------------------

describe('ai-bom: buildAIBOM structure', () => {
  it('produces valid CycloneDX 1.6 envelope', () => {
    const bom = buildAIBOM([]);
    assert.equal(bom.bomFormat, 'CycloneDX');
    assert.equal(bom.specVersion, '1.6');
    assert.equal(bom.version, 1);
  });

  it('has metadata with timestamp and tools', () => {
    const bom = buildAIBOM([]);
    assert.ok(bom.metadata.timestamp.match(/^\d{4}-\d{2}-\d{2}T/));
    assert.equal(bom.metadata.tools[0].name, 'ai-bom-generator');
    assert.equal(bom.metadata.tools[0].vendor, 'llm-security');
  });

  it('includes project metadata from arguments', () => {
    const bom = buildAIBOM([], { name: 'test-project', version: '1.2.3' });
    assert.equal(bom.metadata.component.name, 'test-project');
    assert.equal(bom.metadata.component.version, '1.2.3');
  });

  it('defaults project name to unknown', () => {
    const bom = buildAIBOM([]);
    assert.equal(bom.metadata.component.name, 'unknown');
    assert.equal(bom.metadata.component.version, '0.0.0');
  });

  it('has components and dependencies arrays', () => {
    const bom = buildAIBOM([]);
    assert.ok(Array.isArray(bom.components));
    assert.ok(Array.isArray(bom.dependencies));
  });

  it('passes through components array', () => {
    const components = [
      { type: 'machine-learning-model', name: 'opus', 'bom-ref': 'model-opus' },
    ];
    const bom = buildAIBOM(components);
    assert.equal(bom.components.length, 1);
    assert.equal(bom.components[0].name, 'opus');
  });
});

// ---------------------------------------------------------------------------
// Component discovery tests
// ---------------------------------------------------------------------------

describe('ai-bom: discoverComponents', () => {
  it('discovers hooks from grade-a fixture', async () => {
    const components = await discoverComponents(GRADE_A_FIXTURE);
    const hooks = components.filter(c => c.name.startsWith('hook:'));
    assert.ok(hooks.length >= 4, `Expected >= 4 hooks, got ${hooks.length}`);
  });

  it('discovers knowledge files from plugin root', async () => {
    const components = await discoverComponents(PLUGIN_ROOT);
    const knowledge = components.filter(c => c.name.startsWith('knowledge:'));
    assert.ok(knowledge.length >= 5, `Expected >= 5 knowledge files, got ${knowledge.length}`);
  });

  it('returns empty array for minimal project', async () => {
    const components = await discoverComponents(GRADE_F_FIXTURE);
    // Grade-f has no hooks, no MCP, no knowledge
    assert.ok(Array.isArray(components));
  });

  it('all components have type and name', async () => {
    const components = await discoverComponents(PLUGIN_ROOT);
    for (const c of components) {
      assert.ok(c.type, `Component missing type: ${JSON.stringify(c)}`);
      assert.ok(c.name, `Component missing name: ${JSON.stringify(c)}`);
      assert.ok(c['bom-ref'], `Component missing bom-ref: ${JSON.stringify(c)}`);
    }
  });

  it('component types are valid CycloneDX types', async () => {
    const validTypes = new Set(['machine-learning-model', 'framework', 'library', 'data', 'application']);
    const components = await discoverComponents(PLUGIN_ROOT);
    for (const c of components) {
      assert.ok(validTypes.has(c.type), `Invalid component type: ${c.type} for ${c.name}`);
    }
  });
});

// ---------------------------------------------------------------------------
// Integration: discover + build
// ---------------------------------------------------------------------------

describe('ai-bom: end-to-end', () => {
  it('produces valid CycloneDX BOM from real project', async () => {
    const components = await discoverComponents(PLUGIN_ROOT);
    const bom = buildAIBOM(components, { name: 'llm-security', version: '6.0.0' });

    assert.equal(bom.bomFormat, 'CycloneDX');
    assert.equal(bom.specVersion, '1.6');
    assert.ok(bom.components.length > 0, 'Expected at least one component');
    assert.equal(bom.metadata.component.name, 'llm-security');
  });

  it('empty project produces valid BOM with zero components', async () => {
    const components = await discoverComponents(GRADE_F_FIXTURE);
    const bom = buildAIBOM(components);
    assert.equal(bom.bomFormat, 'CycloneDX');
    assert.ok(Array.isArray(bom.components));
  });
});
