/**
 * models/apiKey.js
 * Manages the api_keys table (Scrape.do, MiniMax, Resend, Stripe).
 */

'use strict';

const { db } = require('../config/db');

const SERVICES = ['scrape_do', 'MiniMax', 'resend', 'stripe'];
const ROTATED_SERVICES = ['scrape_do', 'MiniMax', 'resend']; // stripe doesn't rotate

const insert = db.prepare(`
  INSERT INTO api_keys
    (service, key_value, label, is_active, quota_limit, created_at, updated_at)
  VALUES (@service, @key_value, @label, 1, @quota_limit, @created_at, @updated_at)
`);

const update = db.prepare(`
  UPDATE api_keys
     SET label = COALESCE(?, label),
         key_value = COALESCE(?, key_value),
         is_active = COALESCE(?, is_active),
         quota_limit = COALESCE(?, quota_limit),
         updated_at = ?
   WHERE id = ?
`);

const remove = db.prepare('DELETE FROM api_keys WHERE id = ?');
const findById = db.prepare('SELECT * FROM api_keys WHERE id = ?');

const listByService = db.prepare(`
  SELECT * FROM api_keys WHERE service = ? ORDER BY id ASC
`);

const listAll = db.prepare(`
  SELECT id, service, label, is_active, calls_today, calls_total, quota_limit,
         exhausted_until, last_used_at, last_error, created_at
    FROM api_keys ORDER BY service, id ASC
`);

const listAllWithValues = db.prepare(`
  SELECT * FROM api_keys ORDER BY service, id ASC
`);

const findActiveForService = db.prepare(`
  SELECT * FROM api_keys
   WHERE service = ?
     AND is_active = 1
     AND (exhausted_until IS NULL OR exhausted_until < ?)
   ORDER BY id ASC
`);

const bumpCalls = db.prepare(`
  UPDATE api_keys
     SET calls_today = calls_today + 1,
         calls_total = calls_total + 1,
         last_used_at = ?,
         last_error = COALESCE(?, last_error),
         updated_at = ?
   WHERE id = ?
`);

const markSuccess = db.prepare(`
  UPDATE api_keys
     SET calls_today = calls_today + 1,
         calls_total = calls_total + 1,
         last_used_at = ?,
         last_error = NULL,
         updated_at = ?
   WHERE id = ?
`);

const markExhausted = db.prepare(`
  UPDATE api_keys
     SET exhausted_until = ?,
         last_error = ?,
         updated_at = ?
   WHERE id = ?
`);

const toggle = db.prepare(`
  UPDATE api_keys SET is_active = ?, updated_at = ? WHERE id = ?
`);

function list() { return listAll.all(); }
function listWithValues() { return listAllWithValues.all(); }
function listForService(service) { return listByService.all(service); }
function getById(id) { return findById.get(id); }

function create({ service, key_value, label, quota_limit = 1000 }) {
  if (!SERVICES.includes(service)) throw new Error(`Unknown service: ${service}`);
  // Light prefix/length validation per service. Catches typos and the
  // common mistake of pasting a Stripe key into the Resend slot.
  const v = (key_value || '').trim();
  if (service === 'resend'  && !/^re_[A-Za-z0-9]{8,}$/.test(v)) throw new Error('Resend keys must start with "re_" and be at least 11 chars');
  if (service === 'scrape_do' && !/^[a-f0-9]{20,}$/i.test(v))   throw new Error('Scrape.do keys are 40-char hex');
  if (service === 'MiniMax' && !/^sk-(cp-)?[A-Za-z0-9_-]{8,}$/.test(v)) throw new Error('MiniMax keys start with "sk-" or "sk-cp-"');
  if (service === 'stripe'  && !/^(sk|pk|rk)_(test_|live_)?[A-Za-z0-9]{8,}$/.test(v)) throw new Error('Stripe keys start with sk_, pk_, or rk_');
  const now = Date.now();
  const info = insert.run({
    service, key_value, label: label || `${service}-${Date.now()}`,
    quota_limit,
    created_at: now,
    updated_at: now,
  });
  return findById.get(info.lastInsertRowid);
}

function update_(id, { label, key_value, is_active, quota_limit }) {
  update.run(
    label ?? null,
    key_value ?? null,
    is_active === undefined ? null : (is_active ? 1 : 0),
    quota_limit ?? null,
    Date.now(),
    id
  );
}
function removeKey(id) { remove.run(id); }
function toggleKey(id, isActive) { toggle.run(isActive ? 1 : 0, Date.now(), id); }

function getActiveForService(service) {
  return findActiveForService.all(service, Date.now());
}

function recordSuccess(id) { const now = Date.now(); markSuccess.run(now, now, id); }
function recordFailure(id, error) { const now = Date.now(); bumpCalls.run(now, error, now, id); }
function setExhausted(id, untilTs, error) { const now = Date.now(); markExhausted.run(untilTs, error, now, id); }

function safeKey(row) {
  if (!row) return null;
  // `listAll` deliberately omits key_value from the SELECT to avoid
  // shipping secrets in the response. For the mask, we need the first
  // and last 4 chars — so look up the value separately (admin-only).
  // The caller (`/admin/api-keys`) is gated behind requireSuperadmin2FA,
  // so leaking the mask here is acceptable; the full key_value never
  // leaves the server.
  const fullRow = findById.get(row.id);
  const key_value = fullRow ? fullRow.key_value : null;
  const { key_value: _drop, ...safe } = row;
  safe.masked = key_value ? `${key_value.slice(0, 4)}…${key_value.slice(-4)}` : '';
  return safe;
}

module.exports = {
  SERVICES, ROTATED_SERVICES,
  list, listWithValues, listForService, getById,
  create, update: update_, remove: removeKey, toggleKey,
  getActiveForService, recordSuccess, recordFailure, setExhausted, safeKey,
};
