import { readFileSync } from 'node:fs'
// Resolve the built TS SDK relative to this file so the harness runs on any
// machine. Build it first: `cd licensing-client-ts && npm run build` (see README).
const {
  Verifier,
  PublicKey,
  hashFingerprint,
  parseLicenseKey,
  isExpiredAt,
  hasEntitlement,
} = await import(new URL('../../licensing-client-ts/dist/index.js', import.meta.url))

const vector = JSON.parse(readFileSync(new URL('./vector.json', import.meta.url), 'utf8'))

let failures = 0
function check(name, cond, extra = '') {
  if (cond) {
    console.log(`  OK   ${name}`)
  } else {
    console.log(`  FAIL ${name}${extra ? ': ' + extra : ''}`)
    failures++
  }
}

const toHex = (b) =>
  Array.from(b).map((x) => x.toString(16).padStart(2, '0')).join('')

const verifier = new Verifier(PublicKey.fromPem(vector.publicKeyPem))

function runCase(label, caseData) {
  console.log(`\n== ${label}: parseLicenseKey + verify ==`)
  const expected = caseData.expected
  const parsed = parseLicenseKey(caseData.licenseKey)

  check(`${label}.version`, parsed.payload.version === expected.version,
    `got ${parsed.payload.version}`)
  check(`${label}.productUuid`, parsed.payload.productUuid === expected.productUuid,
    `got ${parsed.payload.productUuid}`)
  check(`${label}.licenseUuid`, parsed.payload.licenseUuid === expected.licenseUuid,
    `got ${parsed.payload.licenseUuid}`)
  check(`${label}.issuedAt`, parsed.payload.issuedAt === expected.issuedAt)
  check(`${label}.expiresAt`, parsed.payload.expiresAt === expected.expiresAt,
    `got ${parsed.payload.expiresAt}`)
  check(`${label}.flags`, parsed.payload.flags === expected.flags,
    `got ${parsed.payload.flags}`)
  check(`${label}.isFingerprintBound`,
    parsed.payload.isFingerprintBound === expected.isFingerprintBound)
  check(`${label}.isTrial`, parsed.payload.isTrial === expected.isTrial)
  check(
    `${label}.entitlements`,
    JSON.stringify(parsed.payload.entitlements) === JSON.stringify(expected.entitlements),
    `got ${JSON.stringify(parsed.payload.entitlements)}`,
  )
  check(
    `${label}.fingerprintHash`,
    toHex(parsed.payload.fingerprintHash) === expected.fingerprintHashHex,
  )

  // Signed bytes length depends on version + entitlements. We just assert
  // that it round-trips the signature check.
  check(`${label}.signature size`, parsed.signature.length === 64)

  try {
    const v = verifier.verify(caseData.licenseKey)
    check(`${label}.verify()`, true)
    check(`${label}.verify productId`, v.productId === expected.productUuid)
    check(`${label}.verify licenseId`, v.licenseId === expected.licenseUuid)
  } catch (e) {
    check(`${label}.verify()`, false, String(e))
  }

  if (expected.isFingerprintBound) {
    try {
      verifier.verifyWithFingerprint(caseData.licenseKey, expected.fingerprintRaw)
      check(`${label}.verifyWithFingerprint correct`, true)
    } catch (e) {
      check(`${label}.verifyWithFingerprint correct`, false, String(e))
    }
    let rejectedWrong = false
    try {
      verifier.verifyWithFingerprint(caseData.licenseKey, 'wrong-fingerprint')
    } catch (e) {
      rejectedWrong = String(e).toLowerCase().includes('fingerprint')
    }
    check(`${label}.verifyWithFingerprint wrong`, rejectedWrong)
  }

  // Entitlement helper.
  for (const slug of expected.entitlements) {
    check(`${label}.hasEntitlement('${slug}')`, hasEntitlement(parsed.payload, slug))
  }
  check(`${label}.hasEntitlement('nonexistent')`,
    !hasEntitlement(parsed.payload, 'definitely-not-a-real-slug'))

  // Expiry helper.
  if (expected.expiresAt > 0) {
    check(`${label}.isExpiredAt(before)`, !isExpiredAt(parsed.payload, expected.expiresAt - 1))
    check(`${label}.isExpiredAt(at)`, isExpiredAt(parsed.payload, expected.expiresAt))
    check(`${label}.isExpiredAt(after)`, isExpiredAt(parsed.payload, expected.expiresAt + 1))
  } else {
    check(`${label}.perpetual never expires`,
      !isExpiredAt(parsed.payload, 2_000_000_000))
  }

  // Tamper check — flip the last base32 character of the signature.
  const orig = caseData.licenseKey
  const lastChar = orig.slice(-1)
  const flipped = lastChar === 'A' ? 'B' : 'A'
  const tampered = orig.slice(0, -1) + flipped
  let tamperRejected = false
  try {
    verifier.verify(tampered)
  } catch {
    tamperRejected = true
  }
  check(`${label}.tampered key rejected`, tamperRejected)
}

runCase('v1', vector.v1)
runCase('v2', vector.v2)
runCase('v2_perpetual_unbound', vector.v2_perpetual_unbound)

console.log('\n== hashFingerprint === Python SHA-256 ==')
check(
  'sha256 matches',
  toHex(hashFingerprint(vector.v1.expected.fingerprintRaw))
    === vector.v1.expected.fingerprintHashHex,
)

console.log('\n== pubkey loaded has correct length ==')
check(
  'public key raw is 32 bytes',
  PublicKey.fromPem(vector.publicKeyPem).raw.length === 32,
)

if (failures > 0) {
  console.log(`\nFAIL: ${failures} check(s) failed`)
  process.exit(1)
}
console.log('\nALL CHECKS PASSED')
