import { describe, test, expect } from 'vitest';
import { deviceIdFrom, licenseGate, type CachedLicense } from './license.js';

describe('deviceIdFrom', () => {
  test('is deterministic for the same inputs', () => {
    expect(deviceIdFrom('mac-1', 'arm64')).toBe(deviceIdFrom('mac-1', 'arm64'));
  });
  test('differs when host or arch differs', () => {
    expect(deviceIdFrom('mac-1', 'arm64')).not.toBe(deviceIdFrom('mac-2', 'arm64'));
    expect(deviceIdFrom('mac-1', 'arm64')).not.toBe(deviceIdFrom('mac-1', 'x64'));
  });
  test('is 16 lowercase hex chars', () => {
    expect(deviceIdFrom('host', 'x64')).toMatch(/^[0-9a-f]{16}$/);
  });
});

const base: CachedLicense = { valid: true, plan: 'paid', expiresAt: null, lastValidatedMs: 1_000_000 };
const cfg = { offlineGraceDays: 30 };
const day = 86_400_000;

describe('licenseGate', () => {
  test('no cache → locked', () => {
    expect(licenseGate(null, base.lastValidatedMs, cfg)).toBe('locked');
  });
  test('invalid cache → locked', () => {
    expect(licenseGate({ ...base, valid: false }, base.lastValidatedMs, cfg)).toBe('locked');
  });
  test('valid, no expiry, fresh → unlocked', () => {
    expect(licenseGate(base, base.lastValidatedMs + day, cfg)).toBe('unlocked');
  });
  test('expired → locked', () => {
    const c = { ...base, expiresAt: new Date(base.lastValidatedMs).toISOString() };
    expect(licenseGate(c, base.lastValidatedMs + day, cfg)).toBe('locked');
  });
  test('unparseable expiry → locked (fail closed, not open)', () => {
    const c = { ...base, expiresAt: 'not-a-date' };
    expect(licenseGate(c, base.lastValidatedMs + day, cfg)).toBe('locked');
  });
  test('offline longer than grace → locked', () => {
    expect(licenseGate(base, base.lastValidatedMs + 31 * day, cfg)).toBe('locked');
  });
  test('offline exactly at grace boundary → unlocked', () => {
    expect(licenseGate(base, base.lastValidatedMs + 30 * day, cfg)).toBe('unlocked');
  });
});
