// KueSync — licensing & 7-day trial
// © 2026 Krometis Analytics. All rights reserved.
//
// Offline-first: license keys are Ed25519-signed tokens verified locally against the bundled
// public key (no license server needed at the venue). A 7-day trial is tracked via a first-run
// timestamp persisted in the module config. Pure crypto below — no Companion imports — so it is
// unit-testable on its own.

import { verify, createPublicKey } from 'node:crypto'
import { PUBLIC_KEY_PEM } from './public-key.js'

export const TRIAL_DAYS = 7
const DAY_MS = 86400_000

/**
 * Verify a license key string ("<payloadB64url>.<sigB64url>").
 * Returns { valid, payload?, reason? }.
 */
export function verifyLicenseKey(key, now = Date.now()) {
  if (!key || typeof key !== 'string' || !key.includes('.')) {
    return { valid: false, reason: 'empty or malformed key' }
  }
  const [payloadB64, sigB64] = key.trim().split('.')
  try {
    const ok = verify(
      null,
      Buffer.from(payloadB64),
      createPublicKey(PUBLIC_KEY_PEM),
      Buffer.from(sigB64, 'base64url')
    )
    if (!ok) return { valid: false, reason: 'signature mismatch' }
    const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'))
    if (payload.product !== 'kuesync') return { valid: false, reason: 'wrong product' }
    if (payload.expiry && now > payload.expiry) {
      return { valid: false, reason: 'license expired', payload }
    }
    return { valid: true, payload }
  } catch (e) {
    return { valid: false, reason: `verify error: ${e.message}` }
  }
}

/**
 * Compute current entitlement from a license key + trial start timestamp.
 *   key        license key string (may be empty)
 *   trialStart ms timestamp of first run (may be undefined)
 *   now        injectable clock for testing
 * Returns { state: 'trial'|'licensed'|'expired', trialDaysLeft, licensedTo, reason }
 */
export function evaluateEntitlement(key, trialStart, now = Date.now()) {
  if (key) {
    const r = verifyLicenseKey(key, now)
    if (r.valid) {
      return { state: 'licensed', trialDaysLeft: 0, licensedTo: r.payload.customer, reason: 'ok' }
    }
    // Invalid/expired key falls back to trial evaluation (so a bad key doesn't grant access).
  }
  if (!trialStart) {
    return { state: 'trial', trialDaysLeft: TRIAL_DAYS, licensedTo: '', reason: 'trial start' }
  }
  const elapsedDays = (now - trialStart) / DAY_MS
  const left = Math.max(0, Math.ceil(TRIAL_DAYS - elapsedDays))
  if (elapsedDays <= TRIAL_DAYS) {
    return { state: 'trial', trialDaysLeft: left, licensedTo: '', reason: 'trial active' }
  }
  return { state: 'expired', trialDaysLeft: 0, licensedTo: '', reason: 'trial expired' }
}

/**
 * Stateful wrapper used by the Companion module. Persists trialStart through a save callback
 * (Companion's saveConfig), and answers "may I send a command right now?".
 */
export class LicenseManager {
  constructor({ key = '', trialStart, saveTrialStart, now = () => Date.now() } = {}) {
    this.key = key
    this.trialStart = trialStart
    this.saveTrialStart = saveTrialStart || (() => {})
    this.now = now
    // Begin the trial clock on first ever run if unlicensed and unstarted.
    if (!this.trialStart && !verifyLicenseKey(this.key).valid) {
      this.trialStart = this.now()
      this.saveTrialStart(this.trialStart)
    }
  }

  setKey(key) {
    this.key = key || ''
  }

  evaluate() {
    return evaluateEntitlement(this.key, this.trialStart, this.now())
  }

  /** Commands are permitted in trial and licensed states, blocked once expired. */
  canSendCommands() {
    return this.evaluate().state !== 'expired'
  }
}
