const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const { validate, encode, parse, isValid, computeCheckCharacter } = require('../dist/euid');

function repoRoot() {
  // reference-implementations/typescript/test/vectors.test.js
  return path.resolve(__dirname, '../../..');
}

const vectorsPath = path.join(repoRoot(), 'test-vectors', 'v2.json');
const vectors = JSON.parse(fs.readFileSync(vectorsPath, 'utf8'));

// ---------------------------------------------------------------------------
// Vector-driven tests
// ---------------------------------------------------------------------------

test('valid production vectors', () => {
  for (const item of vectors.valid_production) {
    const expected = item.canonical ?? item.euid;
    assert.equal(validate(item.euid, { environment: 'production' }), expected);
  }
});

test('valid sandbox vectors', () => {
  for (const item of vectors.valid_sandbox) {
    const prefixes = item.allowed_sandbox_prefixes ? new Set(item.allowed_sandbox_prefixes) : undefined;
    const expected = item.canonical ?? item.euid;
    assert.equal(
      validate(item.euid, { environment: 'sandbox', allowedSandboxPrefixes: prefixes }),
      expected
    );
  }
});

test('invalid vectors', () => {
  for (const item of vectors.invalid) {
    const ctx = item.context;
    const prefixes = ctx.allowed_sandbox_prefixes ? new Set(ctx.allowed_sandbox_prefixes) : undefined;
    assert.throws(() =>
      validate(item.euid, { environment: ctx.environment, allowedSandboxPrefixes: prefixes })
    );
  }
});

// ---------------------------------------------------------------------------
// encode() tests
// ---------------------------------------------------------------------------

test('encode production EUID', () => {
  assert.equal(encode(1, 'TX'), 'TX-1C');
  assert.equal(encode(12345, 'DNA'), 'DNA-C1SJ');
  assert.equal(encode(999999, 'TAG'), 'TAG-YGHZH');
});

test('encode sandbox EUID', () => {
  assert.equal(encode(123, 'TAP', { sandbox: 'X' }), 'X:TAP-3VX');
  assert.equal(encode(456, 'DNA', { sandbox: 'H' }), 'H:DNA-E8E');
});

test('encode rejects short category', () => {
  assert.throws(() => encode(1, 'T'), /2-3/);
});

test('encode rejects long category', () => {
  assert.throws(() => encode(1, 'TXYZ'), /2-3/);
});

test('encode rejects reserved sandbox A-G', () => {
  assert.throws(() => encode(1, 'TX', { sandbox: 'A' }), /reserved/);
  assert.throws(() => encode(1, 'TX', { sandbox: 'G' }), /reserved/);
});

test('encode rejects zero integer', () => {
  assert.throws(() => encode(0, 'TX'), /positive/);
});

test('encode rejects negative integer', () => {
  assert.throws(() => encode(-5, 'TX'), /positive/);
});

// ---------------------------------------------------------------------------
// parse() tests
// ---------------------------------------------------------------------------

test('parse production EUID', () => {
  const result = parse('TX-1C');
  assert.equal(result.sandbox, null);
  assert.equal(result.category, 'TX');
  assert.equal(result.body, '1');
  assert.equal(result.checksum, 'C');
  assert.equal(result.integer, 1);
});

test('parse sandbox EUID', () => {
  const result = parse('X:TAP-3VX');
  assert.equal(result.sandbox, 'X');
  assert.equal(result.category, 'TAP');
  assert.equal(result.body, '3V');
  assert.equal(result.checksum, 'X');
  assert.equal(result.integer, 123);
});

test('parse rejects invalid checksum', () => {
  assert.throws(() => parse('TX-1D'), /checksum/);
});

test('parse rejects lowercase', () => {
  assert.throws(() => parse('tx-1c'), /lowercase/);
});

// ---------------------------------------------------------------------------
// encode → parse round-trip
// ---------------------------------------------------------------------------

test('production round-trip', () => {
  for (const item of vectors.valid_production) {
    const result = parse(item.euid);
    const reEncoded = encode(result.integer, result.category);
    assert.equal(reEncoded, item.euid);
  }
});

test('sandbox round-trip', () => {
  for (const item of vectors.valid_sandbox) {
    const result = parse(item.euid);
    const reEncoded = encode(result.integer, result.category, { sandbox: result.sandbox });
    assert.equal(reEncoded, item.euid);
  }
});

// ---------------------------------------------------------------------------
// isValid / computeCheckCharacter
// ---------------------------------------------------------------------------

test('isValid returns true for valid production EUID', () => {
  assert.equal(isValid('TX-1C', { environment: 'production' }), true);
});

test('isValid returns false for invalid EUID', () => {
  assert.equal(isValid('TX-1D', { environment: 'production' }), false);
});

test('isValid rejects lowercase', () => {
  assert.equal(isValid('tx-1c', { environment: 'production' }), false);
});

test('isValid rejects 1-char category', () => {
  assert.equal(isValid('T-14', { environment: 'production' }), false);
});

test('computeCheckCharacter', () => {
  assert.equal(computeCheckCharacter('TX1'), 'C');
  assert.equal(computeCheckCharacter('XTAP3V'), 'X');
});
