// KueSync — persistent trial store
// © 2026 Krometis Analytics. All rights reserved.
//
// Stores the 7-day trial start OUTSIDE the Companion module folder so re-adding the connection /
// reinstalling can't restart the trial, PLUS a high-water "lastSeen" timestamp so rolling the
// system clock backward can't extend the trial. On macOS: ~/Library/Application Support/KueSync/.
//
// Honest scope: this deters casual tampering. It is NOT tamper-proof — deleting trial.json AND the
// connection config resets the trial, and a sufficiently large forward clock jump expires it early.

import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'

function storeDir() {
  if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'KueSync')
  return path.join(os.homedir(), '.kuesync')
}
export const TRIAL_FILE = path.join(storeDir(), 'trial.json')
// 0.12.52: the per-machine identity private key (0600 file) lives in the SAME reinstall-proof store dir
// as trial.json, so seat activation binds to a stable machine identity that survives a module reinstall.
export function identityKeyPath() { return path.join(storeDir(), 'identity.key') }

function readState() {
  try { return JSON.parse(fs.readFileSync(TRIAL_FILE, 'utf8')) } catch { return {} }
}
function writeState(s) {
  try { fs.mkdirSync(storeDir(), { recursive: true }); fs.writeFileSync(TRIAL_FILE, JSON.stringify({ product: 'kuesync', ...s })); return true }
  catch { return false }
}

export function loadTrialStart() {
  const ts = Number(readState().trialStart)
  return Number.isFinite(ts) ? ts : undefined
}
export function loadLastSeen() {
  const ts = Number(readState().lastSeen)
  return Number.isFinite(ts) ? ts : 0
}
export function persistTrialStart(ts) {
  return writeState({ ...readState(), trialStart: ts })
}
/** Persist lastSeen only if it advances (monotonic high-water mark). */
export function persistLastSeen(ts) {
  const s = readState()
  if (!(Number(ts) > Number(s.lastSeen || 0))) return false
  return writeState({ ...s, lastSeen: ts })
}

// 0.12.52: persisted license-ENFORCEMENT state (revocation + seat activation ladder), stored in the SAME
// reinstall-proof file as trial.json. Mirrors the agent's leader.json lic* fields so the Companion honours a
// server revoke and the ≤24h seat/activation ladder identically. FAIL-OPEN: any read error → all-zero (never
// enforce). suspectedAt / seatSuspectedAt survive restarts so relaunching can't reset a lapse.
export function loadLicState() {
  const s = readState()
  return {
    suspectedAt: Number(s.licSuspectedAt) || 0,
    highWater: Number(s.licHighWater) || 0,
    firstRunAt: Number(s.licFirstRunAt) || 0,
    activatedAt: Number(s.licActivatedAt) || 0,
    seatSuspectedAt: Number(s.licSeatSuspectedAt) || 0,
    seatDenied: !!s.licSeatDenied,
    revoked: !!s.licRevoked,
    keyFp: String(s.licKeyFp || ''),
  }
}
/** Merge a patch of lic* fields into the store (only the keys provided). Never throws. */
export function persistLicState(patch = {}) {
  const s = readState()
  const next = { ...s }
  const map = {
    suspectedAt: 'licSuspectedAt', highWater: 'licHighWater', firstRunAt: 'licFirstRunAt',
    activatedAt: 'licActivatedAt', seatSuspectedAt: 'licSeatSuspectedAt', seatDenied: 'licSeatDenied',
    revoked: 'licRevoked', keyFp: 'licKeyFp',
  }
  for (const k of Object.keys(map)) if (k in patch) next[map[k]] = patch[k]
  return writeState(next)
}

/**
 * Resolve the authoritative trial start as the EARLIEST of the persisted file and the config, so
 * clearing any single source can't restart the trial. Writes the result back.
 */
export function resolveTrialStart(configTS, now = Date.now()) {
  const candidates = [loadTrialStart(), configTS].filter((x) => Number.isFinite(x))
  const effective = candidates.length ? Math.min(...candidates) : now
  persistTrialStart(effective)
  return effective
}
