import { createHash } from 'crypto';
import { hostname, cpus, totalmem, platform, arch } from 'os';
import { writeFileSync, existsSync, readFileSync } from 'fs';
import { join, dirname } from 'path';
import { LicenseGate } from './gate.js';
export { LicenseGate } from './gate.js';
export { InsufficientBalanceError, SpendingLimitError } from './client.js';
let _gate = null;
const PROVISION_TIMEOUT_MS = 10000;
function generateMachineId() {
    // Use stable persisted machine ID if set (Docker entrypoint persists to /data/.machine_id).
    // This ensures the same key is generated across container recreations.
    const stableId = process.env.MACHINE_ID;
    if (stableId) {
        return stableId.slice(0, 16);
    }
    // Fallback: derive from hardware characteristics (non-Docker, stdio mode)
    const data = [hostname(), cpus()[0]?.model ?? '', String(totalmem()), platform(), arch()].join('|');
    return createHash('sha256').update(data).digest('hex').slice(0, 16);
}
/**
 * Auto-provision a license key from the license server.
 * Called when OCR_LICENSE_KEY is not set. Fails fast if server unreachable.
 */
async function provisionKey(serverUrl, machineId) {
    console.error(`[LICENSE] No OCR_LICENSE_KEY set. Auto-provisioning from ${serverUrl}...`);
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), PROVISION_TIMEOUT_MS);
    try {
        const res = await fetch(`${serverUrl}/v1/provision`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ machine_id: machineId }),
            signal: controller.signal,
        });
        clearTimeout(timer);
        if (!res.ok) {
            const text = await res.text().catch(() => '');
            throw new Error(`License server returned ${res.status}: ${text}`);
        }
        const data = (await res.json());
        console.error(`[LICENSE] Auto-provisioned key ${data.key_prefix}... (balance: $${(data.balance_cents / 100).toFixed(2)}, created: ${data.created})`);
        return data.license_key;
    }
    catch (err) {
        clearTimeout(timer);
        const msg = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to auto-provision license key from ${serverUrl}/v1/provision: ${msg}. ` +
            'Set OCR_LICENSE_KEY environment variable manually, or ensure the license server is reachable.');
    }
}
/**
 * Persist the license key to /data/.license_key (or equivalent storagePath parent).
 * This file is read by:
 *   1. The wrapper (via `docker exec cat /data/.license_key`) for bridge auth
 *   2. This module on next startup (survives container recreation)
 */
function persistLicenseKey(storagePath, licenseKey) {
    // When storagePath is '/data' (Docker volume root), join('/data','..') = '/' which is wrong.
    // Use dirname only if storagePath has a meaningful parent, otherwise use storagePath directly.
    const baseDir = dirname(storagePath) === '/' ? storagePath : dirname(storagePath);
    const keyFile = join(baseDir, '.license_key');
    try {
        writeFileSync(keyFile, licenseKey + '\n', { mode: 0o600 });
        console.error(`[LICENSE] Persisted key to ${keyFile}`);
    }
    catch (err) {
        console.error(`[LICENSE] WARN: Could not persist key to ${keyFile}: ${err instanceof Error ? err.message : String(err)}`);
    }
}
export async function initLicenseGate(storagePath) {
    let licenseKey = process.env.OCR_LICENSE_KEY || null;
    const serverUrl = process.env.OCR_LICENSE_SERVER || 'https://license.ocrprovenance.com';
    const machineId = generateMachineId();
    // Auto-provision if no key is configured
    if (!licenseKey) {
        // Check for a previously persisted key first (survives container recreation)
        const readBaseDir = dirname(storagePath) === '/' ? storagePath : dirname(storagePath);
        const keyFile = join(readBaseDir, '.license_key');
        if (existsSync(keyFile)) {
            const persisted = readFileSync(keyFile, 'utf-8').trim();
            if (persisted && persisted.startsWith('lk_')) {
                licenseKey = persisted;
                process.env.OCR_LICENSE_KEY = licenseKey;
                console.error(`[LICENSE] Loaded persisted key from ${keyFile}: ${licenseKey.slice(0, 8)}...`);
            }
        }
    }
    if (!licenseKey) {
        licenseKey = await provisionKey(serverUrl, machineId);
        process.env.OCR_LICENSE_KEY = licenseKey;
        // Persist key for wrapper access and container recreation survival
        persistLicenseKey(storagePath, licenseKey);
    }
    // Validate the key actually works with the current license server.
    // If the signing key changed (e.g., local auto-start with fresh secrets),
    // the old key is invalid. Re-provision to get a valid one.
    try {
        const balanceUrl = `${serverUrl.replace(/\/v1\/?$/, '')}/v1/balance`;
        const checkRes = await fetch(balanceUrl, {
            headers: { 'X-License-Key': licenseKey },
            signal: AbortSignal.timeout(5000),
        });
        if (checkRes.status === 401) {
            console.error(`[LICENSE] Existing key invalid (401). Re-provisioning...`);
            licenseKey = await provisionKey(serverUrl, machineId);
            process.env.OCR_LICENSE_KEY = licenseKey;
            persistLicenseKey(storagePath, licenseKey);
        }
    }
    catch {
        // Server unreachable — proceed with existing key, charge will fail at runtime
    }
    const config = {
        licenseKey,
        serverUrl,
        machineId,
    };
    _gate = new LicenseGate(config, storagePath);
    return _gate;
}
/**
 * Returns the license gate. Throws if not initialized.
 */
export function getLicenseGate() {
    if (!_gate) {
        throw new Error('License gate not initialized. Call initLicenseGate() first. ' +
            'The license server must be reachable before any MCP tools can operate.');
    }
    return _gate;
}
/**
 * Initialize a license gate for integration/E2E tests.
 *
 * When called WITH a serverUrl (from startTestLicenseServer), uses a REAL
 * license server for full end-to-end billing verification.
 *
 * When called WITHOUT a serverUrl, creates a gate with seeded cache for
 * unit tests that only need balance checking (not charge/confirm).
 * charge() will throw if called -- this is intentional (fail-fast).
 */
export function initLicenseGateForTests(storagePath, balanceCents = 100000, options) {
    const config = {
        licenseKey: options?.licenseKey ?? 'lk_test_integration_key',
        serverUrl: options?.serverUrl ?? 'http://127.0.0.1:1',
        machineId: 'test-machine-integration',
    };
    _gate = new LicenseGate(config, storagePath);
    _gate.seedCacheForTests(balanceCents);
    return _gate;
}
//# sourceMappingURL=index.js.map