import { Hono } from 'hono';
import { randomUUID } from 'crypto';
import { query, withTransaction } from '../db.js';
import { licenseKeyAuth } from '../middleware/license-key.js';
import { getSpendingPeriodStart, getSpendingPeriodEnd } from '../spending.js';
import { signBalance, verifyBalance } from '../balance-integrity.js';
/**
 * Server-side pricing table. Cost is determined here — clients MUST NOT hardcode pricing.
 * All costs in cents. Extensible by file_type in the future.
 */
const DEFAULT_COST_CENTS = 3;
function getCostCents(_fileType) {
    // All file types currently cost the same. Server controls pricing.
    return DEFAULT_COST_CENTS;
}
const charge = new Hono();
// POST /v1/charge — reserve a charge (server determines the cost)
charge.post('/charge', licenseKeyAuth, async (c) => {
    const body = await c.req.json();
    if (!body.document_hash || typeof body.document_hash !== 'string') {
        return c.json({ error: 'document_hash is required' }, 400);
    }
    const licenseKeyId = c.get('licenseKeyId');
    const costCents = getCostCents(body.file_type);
    const result = withTransaction(() => {
        // Check balance
        const keyResult = query('SELECT id, balance_cents, balance_hmac, spending_limit_cents, spending_limit_reset_at FROM license_keys WHERE id = ?', [licenseKeyId]);
        if (keyResult.rows.length === 0) {
            return { error: 'License key not found', status: 401 };
        }
        const row = keyResult.rows[0];
        // Verify balance integrity before allowing charge — fail fast on tampered or missing HMAC
        if (!verifyBalance(row.id, row.balance_cents, row.balance_hmac)) {
            console.error(`[FATAL] Balance integrity check failed for key ${row.id}: HMAC mismatch. balance_cents=${row.balance_cents}`);
            return { error: `Balance integrity check failed for key ${row.id}: HMAC mismatch. Balance may have been tampered with.`, status: 403 };
        }
        const balance = row.balance_cents;
        if (balance < costCents) {
            return { error: 'Insufficient balance', status: 402, balance_cents: balance, cost_cents: costCents };
        }
        // Check spending limit
        if (row.spending_limit_cents !== null) {
            const spendingLimit = row.spending_limit_cents;
            const periodStart = getSpendingPeriodStart(row.spending_limit_reset_at);
            const spentResult = query(`SELECT COALESCE(SUM(amount_cents), 0) as total
         FROM charges
         WHERE license_key_id = ? AND status IN ('pending', 'confirmed') AND created_at >= ?`, [licenseKeyId, periodStart]);
            const spent = spentResult.rows[0].total;
            if (spent + costCents > spendingLimit) {
                return {
                    error: 'Monthly spending limit reached',
                    status: 429,
                    spent_cents: spent,
                    limit_cents: spendingLimit,
                    resets_at: getSpendingPeriodEnd(periodStart),
                };
            }
        }
        // Deduct balance and sign the new value in a single UPDATE
        const newBalance = balance - costCents;
        const hmac = signBalance(licenseKeyId, newBalance);
        query('UPDATE license_keys SET balance_cents = ?, balance_hmac = ? WHERE id = ?', [newBalance, hmac, licenseKeyId]);
        // Create charge record
        const chargeId = randomUUID();
        query(`INSERT INTO charges (id, license_key_id, amount_cents, document_hash, machine_id, file_type, file_size_bytes, processing_ms)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [chargeId, licenseKeyId, costCents, body.document_hash, body.machine_id ?? null, body.file_type ?? null, body.file_size_bytes ?? null, body.processing_ms ?? null]);
        return { charge_id: chargeId, cost_cents: costCents, balance_cents: newBalance };
    });
    if ('error' in result) {
        if (result.status === 403) {
            return c.json({ error: result.error }, 403);
        }
        if (result.status === 402) {
            return c.json({ error: result.error, balance_cents: result.balance_cents, cost_cents: result.cost_cents }, 402);
        }
        if (result.status === 429) {
            return c.json({
                error: result.error,
                spent_cents: result.spent_cents,
                limit_cents: result.limit_cents,
                resets_at: result.resets_at,
            }, 429);
        }
        return c.json({ error: result.error }, 401);
    }
    return c.json(result);
});
// POST /v1/confirm — confirm a pending charge
charge.post('/confirm', licenseKeyAuth, async (c) => {
    const body = await c.req.json();
    if (!body.charge_id || typeof body.charge_id !== 'string') {
        return c.json({ error: 'charge_id is required' }, 400);
    }
    const licenseKeyId = c.get('licenseKeyId');
    // Idempotent: already confirmed is OK
    const existing = query('SELECT status FROM charges WHERE id = ? AND license_key_id = ?', [body.charge_id, licenseKeyId]);
    if (existing.rows.length === 0) {
        return c.json({ error: 'Charge not found' }, 404);
    }
    if (existing.rows[0].status === 'confirmed') {
        const balance = query('SELECT balance_cents FROM license_keys WHERE id = ?', [licenseKeyId]);
        return c.json({ balance_cents: balance.rows[0].balance_cents });
    }
    if (existing.rows[0].status !== 'pending') {
        return c.json({ error: `Cannot confirm charge with status '${existing.rows[0].status}'` }, 409);
    }
    query('UPDATE charges SET status = ? WHERE id = ?', ['confirmed', body.charge_id]);
    const balance = query('SELECT balance_cents FROM license_keys WHERE id = ?', [licenseKeyId]);
    return c.json({ balance_cents: balance.rows[0].balance_cents });
});
// POST /v1/refund — refund a charge
charge.post('/refund', licenseKeyAuth, async (c) => {
    const body = await c.req.json();
    if (!body.charge_id || typeof body.charge_id !== 'string') {
        return c.json({ error: 'charge_id is required' }, 400);
    }
    const licenseKeyId = c.get('licenseKeyId');
    const result = withTransaction(() => {
        const existing = query('SELECT status, amount_cents FROM charges WHERE id = ? AND license_key_id = ?', [body.charge_id, licenseKeyId]);
        if (existing.rows.length === 0) {
            return { error: 'Charge not found', status: 404 };
        }
        const chargeRow = existing.rows[0];
        if (chargeRow.status === 'refunded') {
            // Idempotent
            const balance = query('SELECT balance_cents FROM license_keys WHERE id = ?', [licenseKeyId]);
            return { balance_cents: balance.rows[0].balance_cents };
        }
        if (chargeRow.status !== 'pending' && chargeRow.status !== 'confirmed') {
            return { error: `Cannot refund charge with status '${chargeRow.status}'`, status: 409 };
        }
        query('UPDATE charges SET status = ? WHERE id = ?', ['refunded', body.charge_id]);
        // Refund balance: read current, verify HMAC, add refund amount, sign and write in one UPDATE
        const currentKey = query('SELECT id, balance_cents, balance_hmac FROM license_keys WHERE id = ?', [licenseKeyId]);
        if (!verifyBalance(currentKey.rows[0].id, currentKey.rows[0].balance_cents, currentKey.rows[0].balance_hmac)) {
            console.error(`[FATAL] Balance integrity check failed for key ${currentKey.rows[0].id}: HMAC mismatch. balance_cents=${currentKey.rows[0].balance_cents}`);
            return { error: `Balance integrity check failed for key ${currentKey.rows[0].id}: HMAC mismatch. Balance may have been tampered with.`, status: 403 };
        }
        const newBalance = currentKey.rows[0].balance_cents + chargeRow.amount_cents;
        const hmac = signBalance(currentKey.rows[0].id, newBalance);
        query('UPDATE license_keys SET balance_cents = ?, balance_hmac = ? WHERE id = ?', [newBalance, hmac, currentKey.rows[0].id]);
        return { balance_cents: newBalance };
    });
    if ('error' in result) {
        const status = result.status;
        if (status === 403)
            return c.json({ error: result.error }, 403);
        if (status === 409)
            return c.json({ error: result.error }, 409);
        return c.json({ error: result.error }, 404);
    }
    return c.json(result);
});
// POST /v1/sync — sync offline charges (server determines cost)
charge.post('/sync', licenseKeyAuth, async (c) => {
    const body = await c.req.json();
    if (!Array.isArray(body.charges)) {
        return c.json({ error: 'charges array is required' }, 400);
    }
    const charges = body.charges;
    const licenseKeyId = c.get('licenseKeyId');
    try {
        const synced = withTransaction(() => {
            let accepted = 0;
            let rejected = 0;
            for (const item of charges) {
                // Skip if charge already exists (idempotent)
                const existing = query('SELECT id FROM charges WHERE id = ?', [item.id]);
                if (existing.rows.length > 0)
                    continue;
                const costCents = getCostCents(item.file_type);
                // Check balance BEFORE deducting — fail fast, no MAX(0) workaround
                const keyResult = query('SELECT id, balance_cents, balance_hmac FROM license_keys WHERE id = ?', [licenseKeyId]);
                // Verify balance integrity before allowing sync deduction
                if (!verifyBalance(keyResult.rows[0].id, keyResult.rows[0].balance_cents, keyResult.rows[0].balance_hmac)) {
                    console.error(`[FATAL] Balance integrity check failed for key ${keyResult.rows[0].id}: HMAC mismatch. balance_cents=${keyResult.rows[0].balance_cents}`);
                    throw new Error(`Balance integrity check failed for key ${keyResult.rows[0].id}: HMAC mismatch. Balance may have been tampered with.`);
                }
                if (keyResult.rows[0].balance_cents < costCents) {
                    rejected++;
                    continue;
                }
                const status = item.confirmed ? 'confirmed' : 'pending';
                query(`INSERT INTO charges (id, license_key_id, amount_cents, document_hash, status)
           VALUES (?, ?, ?, ?, ?)`, [item.id, licenseKeyId, costCents, item.document_hash, status]);
                // Deduct balance and sign in a single UPDATE
                const syncNewBalance = keyResult.rows[0].balance_cents - costCents;
                const syncHmac = signBalance(licenseKeyId, syncNewBalance);
                query('UPDATE license_keys SET balance_cents = ?, balance_hmac = ? WHERE id = ?', [syncNewBalance, syncHmac, licenseKeyId]);
                accepted++;
            }
            return { accepted, rejected };
        });
        const balance = query('SELECT balance_cents FROM license_keys WHERE id = ?', [licenseKeyId]);
        return c.json({
            synced: synced.accepted,
            rejected: synced.rejected,
            balance_cents: balance.rows[0].balance_cents,
        });
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        if (message.includes('Balance integrity check failed')) {
            return c.json({ error: message }, 403);
        }
        throw err;
    }
});
export default charge;
//# sourceMappingURL=charge.js.map