import { Hono } from 'hono';
import { query, withTransaction } from '../db.js';
import { sessionAuth } from '../middleware/session.js';
import { adminGuard } from '../middleware/admin.js';
import { signBalance, verifyBalance } from '../balance-integrity.js';
const admin = new Hono();
// All admin routes require session + admin guard
admin.use('/*', sessionAuth, adminGuard);
// GET /v1/admin/overview
admin.get('/overview', async (c) => {
    const overview = query(`SELECT
      (SELECT COUNT(*) FROM users) as total_users,
      (SELECT COALESCE(SUM(amount_cents), 0) FROM payments) as total_revenue_cents,
      (SELECT COUNT(*) FROM charges) as total_charges,
      (SELECT SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) FROM charges) as confirmed_charges,
      (SELECT SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) FROM charges) as pending_charges,
      (SELECT SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) FROM charges) as refunded_charges`);
    const row = overview.rows[0];
    return c.json({
        total_users: row.total_users,
        total_revenue_cents: row.total_revenue_cents,
        total_charges: row.total_charges,
        confirmed_charges: row.confirmed_charges ?? 0,
        pending_charges: row.pending_charges ?? 0,
        refunded_charges: row.refunded_charges ?? 0,
    });
});
// GET /v1/admin/users?search=&page=1&limit=20
admin.get('/users', async (c) => {
    const search = c.req.query('search') ?? '';
    const page = Math.max(1, parseInt(c.req.query('page') ?? '1', 10));
    const limit = Math.min(100, Math.max(1, parseInt(c.req.query('limit') ?? '20', 10)));
    const offset = (page - 1) * limit;
    let countSql = 'SELECT COUNT(*) as total FROM users u';
    let listSql = `SELECT u.id, u.email, u.stripe_customer_id, u.created_at,
    lk.balance_cents, lk.key_prefix,
    (SELECT COUNT(*) FROM charges c WHERE c.license_key_id = lk.id) as total_charges,
    (SELECT COALESCE(SUM(p.amount_cents), 0) FROM payments p WHERE p.user_id = u.id) as total_paid_cents
    FROM users u
    LEFT JOIN license_keys lk ON lk.user_id = u.id AND lk.status = 'active'`;
    const params = [];
    if (search) {
        const whereClause = ` WHERE u.email LIKE ? ESCAPE '\\'`;
        countSql += whereClause;
        listSql += whereClause;
        const escaped = search.replace(/[%_\\]/g, '\\$&');
        params.push(`%${escaped}%`);
    }
    listSql += ' ORDER BY u.created_at DESC LIMIT ? OFFSET ?';
    const totalResult = query(countSql, params);
    const usersResult = query(listSql, [...params, limit, offset]);
    return c.json({
        users: usersResult.rows,
        page,
        limit,
        total: totalResult.rows[0].total,
        total_pages: Math.ceil(totalResult.rows[0].total / limit),
    });
});
// GET /v1/admin/users/:id
admin.get('/users/:id', async (c) => {
    const userId = c.req.param('id');
    const userResult = query('SELECT * FROM users WHERE id = ?', [userId]);
    if (userResult.rows.length === 0) {
        return c.json({ error: 'User not found' }, 404);
    }
    const keys = query('SELECT id, key_prefix, balance_cents, status, created_at FROM license_keys WHERE user_id = ?', [userId]);
    const charges = query(`SELECT id, amount_cents, document_hash, status, file_type, created_at
     FROM charges WHERE license_key_id IN (SELECT id FROM license_keys WHERE user_id = ?)
     ORDER BY created_at DESC LIMIT 50`, [userId]);
    const payments = query('SELECT id, amount_cents, stripe_payment_intent_id, created_at FROM payments WHERE user_id = ? ORDER BY created_at DESC', [userId]);
    return c.json({
        user: userResult.rows[0],
        license_keys: keys.rows,
        recent_charges: charges.rows,
        payments: payments.rows,
    });
});
// GET /v1/admin/timeseries?days=7
admin.get('/timeseries', async (c) => {
    const days = Math.min(365, Math.max(1, parseInt(c.req.query('days') ?? '7', 10)));
    // Generate date range in code (replaces generate_series)
    const dates = [];
    const now = new Date();
    for (let i = days; i >= 0; i--) {
        const d = new Date(now);
        d.setDate(d.getDate() - i);
        dates.push(d.toISOString().slice(0, 10));
    }
    const startDate = dates[0];
    const revenueData = query(`SELECT date(created_at) as day, SUM(amount_cents) as total
     FROM payments WHERE created_at >= ? GROUP BY date(created_at)`, [startDate]);
    const chargeData = query(`SELECT date(created_at) as day, COUNT(*) as total
     FROM charges WHERE status = 'confirmed' AND created_at >= ? GROUP BY date(created_at)`, [startDate]);
    const signupData = query(`SELECT date(created_at) as day, COUNT(*) as total
     FROM users WHERE created_at >= ? GROUP BY date(created_at)`, [startDate]);
    // Merge into date range
    const revenueMap = new Map(revenueData.rows.map(r => [r.day, r.total]));
    const chargeMap = new Map(chargeData.rows.map(r => [r.day, r.total]));
    const signupMap = new Map(signupData.rows.map(r => [r.day, r.total]));
    const timeseries = dates.map(day => ({
        date: day,
        revenue_cents: revenueMap.get(day) ?? 0,
        charges: chargeMap.get(day) ?? 0,
        signups: signupMap.get(day) ?? 0,
    }));
    return c.json({ timeseries, days });
});
// POST /v1/admin/revoke — Revoke a license key
admin.post('/revoke', async (c) => {
    const body = await c.req.json();
    if (!body.license_key_id || typeof body.license_key_id !== 'string') {
        return c.json({ error: 'license_key_id is required' }, 400);
    }
    const existing = query('SELECT id, key_prefix, status FROM license_keys WHERE id = ?', [body.license_key_id]);
    if (existing.rows.length === 0) {
        return c.json({ error: `License key ${body.license_key_id} not found` }, 404);
    }
    if (existing.rows[0].status === 'revoked') {
        return c.json({ error: `License key ${existing.rows[0].key_prefix}... is already revoked` }, 409);
    }
    query('UPDATE license_keys SET status = ? WHERE id = ?', ['revoked', body.license_key_id]);
    console.error(`[admin] Revoked license key ${existing.rows[0].key_prefix}... (id=${body.license_key_id}) reason=${body.reason ?? 'none'}`);
    return c.json({
        revoked: true,
        license_key_id: body.license_key_id,
        key_prefix: existing.rows[0].key_prefix,
    });
});
// POST /v1/admin/adjust-balance — Adjust a license key's balance
admin.post('/adjust-balance', async (c) => {
    const body = await c.req.json();
    if (!body.license_key_id || typeof body.license_key_id !== 'string') {
        return c.json({ error: 'license_key_id is required' }, 400);
    }
    if (body.amount_cents === undefined || typeof body.amount_cents !== 'number' || !Number.isInteger(body.amount_cents)) {
        return c.json({ error: 'amount_cents is required and must be an integer (positive to credit, negative to debit)' }, 400);
    }
    if (!body.reason || typeof body.reason !== 'string') {
        return c.json({ error: 'reason is required for audit trail' }, 400);
    }
    // Capture validated values for use inside the transaction closure
    const licenseKeyId = body.license_key_id;
    const amountCents = body.amount_cents;
    const reason = body.reason;
    // Wrap read-verify-write in a transaction to prevent concurrent charge interleaving
    const result = withTransaction(() => {
        const existing = query('SELECT id, key_prefix, balance_cents, balance_hmac, status FROM license_keys WHERE id = ?', [licenseKeyId]);
        if (existing.rows.length === 0) {
            return { error: `License key ${licenseKeyId} not found`, status: 404 };
        }
        if (existing.rows[0].status !== 'active') {
            return { error: `Cannot adjust balance on ${existing.rows[0].status} key`, status: 409 };
        }
        // Verify HMAC before adjusting — fail fast on tampered balances
        if (!verifyBalance(existing.rows[0].id, existing.rows[0].balance_cents, existing.rows[0].balance_hmac)) {
            console.error(`[FATAL] Balance integrity check failed for key ${existing.rows[0].id} during admin adjust: HMAC mismatch. balance_cents=${existing.rows[0].balance_cents}`);
            return { error: `Balance integrity check failed for key ${existing.rows[0].id}: HMAC mismatch. Balance may have been tampered with.`, status: 403 };
        }
        const newBalance = existing.rows[0].balance_cents + amountCents;
        if (newBalance < 0) {
            return {
                error: `Adjustment would result in negative balance (current: ${existing.rows[0].balance_cents}, adjustment: ${amountCents})`,
                status: 400,
            };
        }
        const adjustHmac = signBalance(licenseKeyId, newBalance);
        query('UPDATE license_keys SET balance_cents = ?, balance_hmac = ? WHERE id = ?', [newBalance, adjustHmac, licenseKeyId]);
        console.error(`[admin] Adjusted balance for ${existing.rows[0].key_prefix}... by ${amountCents} cents (${existing.rows[0].balance_cents} → ${newBalance}). Reason: ${reason}`);
        return {
            license_key_id: licenseKeyId,
            key_prefix: existing.rows[0].key_prefix,
            previous_balance_cents: existing.rows[0].balance_cents,
            adjustment_cents: amountCents,
            new_balance_cents: newBalance,
            reason,
        };
    });
    if ('status' in result) {
        return c.json({ error: result.error }, result.status);
    }
    return c.json(result);
});
// GET /v1/admin/keys — List all license keys with status and balance
admin.get('/keys', async (c) => {
    const status = c.req.query('status') ?? '';
    const page = Math.max(1, parseInt(c.req.query('page') ?? '1', 10));
    const limit = Math.min(100, Math.max(1, parseInt(c.req.query('limit') ?? '50', 10)));
    const offset = (page - 1) * limit;
    let countSql = 'SELECT COUNT(*) as total FROM license_keys';
    let listSql = `SELECT lk.id, lk.key_prefix, lk.balance_cents, lk.status, lk.tier, lk.created_at,
    u.email,
    (SELECT COUNT(*) FROM charges c WHERE c.license_key_id = lk.id AND c.status = 'confirmed') as total_charges
    FROM license_keys lk
    JOIN users u ON u.id = lk.user_id`;
    const params = [];
    if (status === 'active' || status === 'revoked') {
        countSql += ' WHERE status = ?';
        listSql += ' WHERE lk.status = ?';
        params.push(status);
    }
    listSql += ' ORDER BY lk.created_at DESC LIMIT ? OFFSET ?';
    const totalResult = query(countSql, params);
    const keysResult = query(listSql, [...params, limit, offset]);
    return c.json({
        keys: keysResult.rows,
        page,
        limit,
        total: totalResult.rows[0].total,
        total_pages: Math.ceil(totalResult.rows[0].total / limit),
    });
});
// GET /v1/admin/usage-report — Usage analytics
admin.get('/usage-report', async (c) => {
    const days = Math.min(365, Math.max(1, parseInt(c.req.query('days') ?? '30', 10)));
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - days);
    const startStr = startDate.toISOString();
    const totalCharges = query(`SELECT COUNT(*) as count, COALESCE(SUM(amount_cents), 0) as total_cents
     FROM charges WHERE status = 'confirmed' AND created_at >= ?`, [startStr]);
    const totalPayments = query(`SELECT COUNT(*) as count, COALESCE(SUM(amount_cents), 0) as total_cents
     FROM payments WHERE created_at >= ?`, [startStr]);
    const activeKeys = query("SELECT COUNT(*) as count FROM license_keys WHERE status = 'active'");
    const topUsers = query(`SELECT u.email, lk.key_prefix, COUNT(c.id) as charge_count, SUM(c.amount_cents) as total_spent_cents
     FROM charges c
     JOIN license_keys lk ON lk.id = c.license_key_id
     JOIN users u ON u.id = lk.user_id
     WHERE c.status = 'confirmed' AND c.created_at >= ?
     GROUP BY lk.id
     ORDER BY total_spent_cents DESC
     LIMIT 10`, [startStr]);
    return c.json({
        period_days: days,
        charges: { count: totalCharges.rows[0].count, total_cents: totalCharges.rows[0].total_cents },
        payments: { count: totalPayments.rows[0].count, total_cents: totalPayments.rows[0].total_cents },
        active_keys: activeKeys.rows[0].count,
        top_users: topUsers.rows,
    });
});
export default admin;
//# sourceMappingURL=admin.js.map