import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { join } from 'node:path';
import { rm, stat, readFile, mkdir } from 'node:fs/promises';
import { tmpdir, homedir } from 'node:os';
import {
  saveBaseline,
  loadBaseline,
  listBaselines,
  deleteBaseline,
  getBaselinesDir,
} from '../../scanners/lib/baseline.mjs';

// We test against the real baselines dir but use unique names to avoid collisions.
const TEST_PREFIX = `_test_${Date.now()}_`;

function makeTestEnvelope(findingCount = 3) {
  const findings = Array.from({ length: findingCount }, (_, i) => ({
    id: `CA-CML-${String(i + 1).padStart(3, '0')}`,
    scanner: 'CML',
    severity: 'low',
    title: `Finding ${i + 1}`,
    file: 'CLAUDE.md',
  }));
  return {
    meta: { target: '/test/path', timestamp: new Date().toISOString(), version: '2.0.0', tool: 'config-audit' },
    scanners: [{
      scanner: 'CML',
      status: 'ok',
      files_scanned: 1,
      duration_ms: 5,
      findings,
      counts: { critical: 0, high: 0, medium: 0, low: findingCount, info: 0 },
    }],
    aggregate: { total_findings: findingCount, counts: { critical: 0, high: 0, medium: 0, low: findingCount, info: 0 }, risk_score: findingCount, risk_band: 'Low', verdict: 'PASS', scanners_ok: 1, scanners_error: 0, scanners_skipped: 0 },
  };
}

// Cleanup helper
const savedNames = [];
async function cleanup() {
  for (const name of savedNames) {
    await deleteBaseline(name);
  }
  savedNames.length = 0;
}

describe('saveBaseline', () => {
  afterEach(cleanup);

  it('writes a file and returns path', async () => {
    const name = `${TEST_PREFIX}save1`;
    savedNames.push(name);

    const envelope = makeTestEnvelope(2);
    const result = await saveBaseline(envelope, name);

    assert.ok(result.path.endsWith(`${name}.json`));
    assert.equal(result.name, name);

    // Verify file exists
    const s = await stat(result.path);
    assert.ok(s.isFile());
  });

  it('includes _baseline metadata', async () => {
    const name = `${TEST_PREFIX}meta`;
    savedNames.push(name);

    const envelope = makeTestEnvelope(5);
    await saveBaseline(envelope, name);

    const loaded = await loadBaseline(name);
    assert.ok(loaded._baseline);
    assert.ok(loaded._baseline.saved_at);
    assert.equal(loaded._baseline.target_path, '/test/path');
    assert.equal(loaded._baseline.finding_count, 5);
    assert.equal(typeof loaded._baseline.score, 'number');
  });

  it('defaults name to "default"', async () => {
    const name = `${TEST_PREFIX}default_test`;
    savedNames.push(name);
    // We won't actually use the literal 'default' to avoid interfering with real baselines
    const result = await saveBaseline(makeTestEnvelope(), name);
    assert.equal(result.name, name);
  });

  it('overwrites existing baseline with same name', async () => {
    const name = `${TEST_PREFIX}overwrite`;
    savedNames.push(name);

    await saveBaseline(makeTestEnvelope(2), name);
    await saveBaseline(makeTestEnvelope(7), name);

    const loaded = await loadBaseline(name);
    assert.equal(loaded._baseline.finding_count, 7);
  });
});

describe('loadBaseline', () => {
  afterEach(cleanup);

  it('loads a previously saved baseline', async () => {
    const name = `${TEST_PREFIX}load`;
    savedNames.push(name);

    const envelope = makeTestEnvelope(3);
    await saveBaseline(envelope, name);

    const loaded = await loadBaseline(name);
    assert.ok(loaded);
    assert.equal(loaded.aggregate.total_findings, 3);
    assert.equal(loaded.meta.target, '/test/path');
  });

  it('returns null for unknown name', async () => {
    const result = await loadBaseline(`${TEST_PREFIX}nonexistent_${Date.now()}`);
    assert.equal(result, null);
  });

  it('preserves all scanner data', async () => {
    const name = `${TEST_PREFIX}preserve`;
    savedNames.push(name);

    const envelope = makeTestEnvelope(1);
    await saveBaseline(envelope, name);

    const loaded = await loadBaseline(name);
    assert.equal(loaded.scanners.length, 1);
    assert.equal(loaded.scanners[0].scanner, 'CML');
    assert.equal(loaded.scanners[0].findings.length, 1);
  });
});

describe('listBaselines', () => {
  afterEach(cleanup);

  it('lists saved baselines', async () => {
    const name = `${TEST_PREFIX}list`;
    savedNames.push(name);

    await saveBaseline(makeTestEnvelope(4), name);

    const result = await listBaselines();
    assert.ok(Array.isArray(result.baselines));
    const found = result.baselines.find(b => b.name === name);
    assert.ok(found, 'Should find the saved baseline in list');
    assert.equal(found.findingCount, 4);
    assert.ok(found.savedAt);
  });

  it('returns empty array when no baselines', async () => {
    // This test just verifies the function doesn't crash
    const result = await listBaselines();
    assert.ok(Array.isArray(result.baselines));
  });
});

describe('deleteBaseline', () => {
  it('deletes an existing baseline', async () => {
    const name = `${TEST_PREFIX}delete`;

    await saveBaseline(makeTestEnvelope(), name);
    const result = await deleteBaseline(name);
    assert.equal(result.deleted, true);

    const loaded = await loadBaseline(name);
    assert.equal(loaded, null);
  });

  it('returns false for non-existent baseline', async () => {
    const result = await deleteBaseline(`${TEST_PREFIX}nope_${Date.now()}`);
    assert.equal(result.deleted, false);
  });
});
