// DDC Automation client — runs INSIDE the Worker. Reads config from
// `third_party_integrations` D1 table (provider='ddc') so admins can
// rotate tokens / move domains from the dashboard without a redeploy.
// Do NOT import from the extension; the token must never reach a browser.
//
// Usage:
//   import { ddcSubmit, ddcPoll, loadDdcConfig } from '../lib/ddc.js';
//   const job = await ddcSubmit(env, 'screenshot',
//     { url: 'https://x.com' },
//     { purpose: 'post-verification', userId, postId });
//
// Webhook signature verification: see verifyDdcWebhook() below.
// Docs: docs/automation-integration.md

// loadDdcConfig: returns { origin, token, webhookSecret } or null if
// the integration row doesn't exist. Caller is responsible for caching
// across multiple ddcSubmit calls in a single request if it matters.
// The caller must provide decryptSecret (avoids a circular import with
// worker.js). Worker calls ddcSubmit which calls this internally.
async function loadDdcConfig(env, decryptSecret) {
  const row = await env.DB.prepare(
    `SELECT origin, secrets_cipher, secrets_iv FROM third_party_integrations WHERE provider = ?`
  ).bind('ddc').first();
  if (!row) return null;
  let json;
  try {
    const plain = await decryptSecret(env, row.secrets_cipher, row.secrets_iv);
    json = JSON.parse(plain);
  } catch {
    return null;
  }
  return { origin: row.origin, token: json.token, webhookSecret: json.webhookSecret };
}
export { loadDdcConfig };

function callbackUrl(env) {
  return env.DDC_CALLBACK_URL || 'https://fb-poster-sync.idandada.workers.dev/webhooks/ddc';
}

// Submit a job. Caller passes decryptSecret to avoid circular imports.
// Returns { ok, jobId?, status?, error? }.
export async function ddcSubmit(env, decryptSecret, type, input, metadata = {}) {
  const cfg = await loadDdcConfig(env, decryptSecret);
  if (!cfg?.token) return { ok: false, error: 'DDC not configured — set in Dashboard → Integrations' };
  const body = { type, input, callbackUrl: callbackUrl(env), metadata };
  try {
    const res = await fetch(`${cfg.origin}/api/automation/jobs`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${cfg.token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });
    const text = await res.text();
    let json = null;
    try { json = JSON.parse(text); } catch {}
    if (!res.ok || !json?.success) {
      return {
        ok: false,
        statusCode: res.status,
        error: json?.error || json?.message || text.slice(0, 300) || `HTTP ${res.status}`,
        retryAfterSec: Number(res.headers.get('Retry-After')) || null,
      };
    }
    return { ok: true, ...json.data };
  } catch (e) {
    return { ok: false, error: String(e?.message || e) };
  }
}

// Poll a single job. Returns { ok, job?, error? }.
export async function ddcPoll(env, decryptSecret, jobId) {
  const cfg = await loadDdcConfig(env, decryptSecret);
  if (!cfg?.token) return { ok: false, error: 'DDC not configured' };
  try {
    const res = await fetch(`${cfg.origin}/api/automation/jobs/${encodeURIComponent(jobId)}`, {
      headers: { 'Authorization': `Bearer ${cfg.token}` },
    });
    const text = await res.text();
    let json = null;
    try { json = JSON.parse(text); } catch {}
    if (!res.ok || !json?.success) {
      return { ok: false, statusCode: res.status, error: json?.error || `HTTP ${res.status}` };
    }
    return { ok: true, job: json.data };
  } catch (e) {
    return { ok: false, error: String(e?.message || e) };
  }
}

// Verify the X-DDC-Signature header against the raw body.
// Returns true iff HMAC-SHA256(secret, rawBody) === sig (constant-time).
export async function verifyDdcWebhook(rawBody, signature, secret) {
  if (!signature || !secret) return false;
  try {
    const key = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(secret),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign'],
    );
    const computedBuf = await crypto.subtle.sign(
      'HMAC', key, new TextEncoder().encode(rawBody),
    );
    const computed = [...new Uint8Array(computedBuf)]
      .map(b => b.toString(16).padStart(2, '0')).join('');
    // Constant-time compare (length check first, then char-by-char XOR).
    if (computed.length !== signature.length) return false;
    let diff = 0;
    for (let i = 0; i < computed.length; i++) {
      diff |= computed.charCodeAt(i) ^ signature.charCodeAt(i);
    }
    return diff === 0;
  } catch {
    return false;
  }
}

// Job-type constants — keep callers from typoing the type field.
export const DDC = Object.freeze({
  A11Y_KEYBOARD: 'a11y-keyboard-audit',
  A11Y_AXE:      'a11y-axe',
  AUDIO_EXTRACT: 'audio-extract',
  VIDEO_FRAMES:  'video-frames',
  SCREENSHOT:    'screenshot',
  PDF:           'pdf',
  SCRAPE:        'scrape',
});
