/**
 * Resend email tool adapters.
 *
 * Implements two tools:
 *   - emailSend(input, options)        — POST /emails (irreversible_external)
 *   - emailStatusLookup(input, options) — GET /emails/{id} (low risk)
 *
 * Credential resolution: vault first (getSecret), falling back to process.env
 * for backwards compatibility during the migration window.
 *
 * Security model:
 *   - RESEND_API_KEY is never logged, echoed, or included in returned errors.
 *   - email_send is risk_level: 'irreversible_external' — email cannot be recalled.
 *   - Allowlist enforces recipient_domain via executeRegistryTool before this adapter runs.
 *   - wrapFetch records every outbound call to tool_egress for observability.
 *
 * recipient_domain two-step validation:
 *   The allowlist gate in executeRegistryTool derives the domain from input.to and
 *   checks it against the tool_allowlists table BEFORE this adapter is called.
 *   This adapter performs a second check: it re-derives the domain from input.to and
 *   verifies it matches input.recipient_domain. This prevents an LLM from passing
 *   recipient_domain: 'good.com' (allowlist passes) while to: 'user@evil.com'
 *   (email actually goes elsewhere).
 *
 *   If the two-step check detects a mismatch, the adapter throws with a clear
 *   message telling the caller to set recipient_domain to the derived value.
 *
 *   TODO: Long-term, recipient_domain derivation should be centralised inside
 *   executeRegistryTool so per-adapter checks are not needed. That requires a
 *   registry-level input transformation hook, which was deferred to avoid
 *   coordination conflicts with parallel Phase A builders.
 */

import { getSecret } from '../../secrets.js';
import { wrapFetch } from '../../egress.js';

const RESEND_API_BASE = 'https://api.resend.com';
const RESEND_TEST_SENDER = 'onboarding@resend.dev';

function resolveCredential(name, options = {}) {
  try {
    const secret = getSecret(name, {
      agent_id: options.agent_id,
      project_id: options.project_id,
    });
    if (secret) return secret;
  } catch {
    // Vault not ready — fall through to process.env.
  }
  return process.env[name] || null;
}

function requireCredential(name, options = {}) {
  const value = resolveCredential(name, options);
  if (!value) throw new Error(`${name} not configured`);
  return value;
}

/**
 * Derive the recipient domain from an email address.
 * Returns the lowercased domain portion after '@', or null if the address
 * does not contain '@'.
 *
 * @param {string} email
 * @returns {string|null}
 */
function deriveDomain(email) {
  const str = String(email || '').trim();
  const atIdx = str.lastIndexOf('@');
  if (atIdx < 0) return null;
  return str.slice(atIdx + 1).toLowerCase().trim();
}

/**
 * Extract a safe error message from a Resend error response.
 * Never includes the API key or Authorization header value.
 *
 * @param {Response} res
 * @returns {Promise<string>}
 */
async function resendErrorMessage(res) {
  try {
    const body = await res.json();
    // Resend error shape: { statusCode, name, message }
    return body?.message || body?.name || `HTTP ${res.status}`;
  } catch {
    return `HTTP ${res.status}`;
  }
}

/**
 * Send a transactional email via Resend.
 *
 * Required input fields:
 *   - to: single recipient email address
 *   - subject: email subject line
 *   - recipient_domain: must match the domain in `to` (two-step validation)
 *
 * Optional input fields:
 *   - from: sender address (defaults to RESEND_FROM_EMAIL secret/env)
 *   - text: plain-text body
 *   - html: HTML body
 *   - reply_to: reply-to address
 *
 * Either `text` or `html` must be provided.
 *
 * Returns: { ok: true, id, to, recipient_domain }
 * Never echoes text/html/subject in the response.
 *
 * @param {object} input
 * @param {{ agent_id?: string, owner_trusted?: boolean }} options
 */
export async function emailSend(input = {}, options = {}) {
  const to = String(input.to || '').trim();
  if (!to) throw new Error('"to" is required');

  const subject = String(input.subject || '').trim();
  if (!subject) throw new Error('"subject" is required');

  if (!input.text && !input.html) {
    throw new Error('Either "text" or "html" body content is required');
  }

  // ── Two-step recipient_domain validation ────────────────────────────────
  // Step 1: derive the actual domain from input.to.
  const derivedDomain = deriveDomain(to);
  if (!derivedDomain) {
    throw new Error(`"to" must be a valid email address containing "@"; got: ${to}`);
  }

  // Step 2: verify it matches the allowlist-checked recipient_domain field.
  // The allowlist gate already checked input.recipient_domain against the
  // allowlist table. If input.recipient_domain is absent or mismatched, the
  // LLM could have bypassed the allowlist by crafting input.to separately.
  const declaredDomain = input.recipient_domain
    ? String(input.recipient_domain).toLowerCase().trim()
    : null;

  if (!declaredDomain) {
    throw new Error(
      `"recipient_domain" is required and must be set to "${derivedDomain}" ` +
      `(the domain of the "to" address). ` +
      `Retry with: { ..., recipient_domain: "${derivedDomain}" }`
    );
  }

  if (declaredDomain !== derivedDomain) {
    throw new Error(
      `"recipient_domain" mismatch: declared "${declaredDomain}" but "to" address ` +
      `"${to}" has domain "${derivedDomain}". ` +
      `Set recipient_domain: "${derivedDomain}" and retry.`
    );
  }
  // ── End two-step validation ─────────────────────────────────────────────

  const apiKey = requireCredential('RESEND_API_KEY', options);
  const agentId = options.agent_id ?? null;

  // Resolve sender address.
  let from = input.from ? String(input.from).trim() : null;
  if (!from) {
    from = resolveCredential('RESEND_FROM_EMAIL', options);
  }
  if (!from) {
    // Neither vault secret nor env is configured. Using the test sender is only
    // safe for verified Resend accounts with onboarding@resend.dev access.
    // We surface a clear error rather than silently defaulting.
    throw new Error(
      'Sender address not configured. Set RESEND_FROM_EMAIL via the secrets vault ' +
      `or pass "from" explicitly in the input. ` +
      `Resend's test sender (${RESEND_TEST_SENDER}) requires a verified account.`
    );
  }

  const body = {
    from,
    to,
    subject,
    ...(input.text ? { text: String(input.text) } : {}),
    ...(input.html ? { html: String(input.html) } : {}),
    ...(input.reply_to ? { reply_to: String(input.reply_to) } : {}),
  };

  const wrappedFetch = wrapFetch('email_send', agentId);
  const res = await wrappedFetch(`${RESEND_API_BASE}/emails`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const message = await resendErrorMessage(res);
    // Ensure API key never surfaces in error messages.
    throw new Error(`Resend API error (${res.status}): ${message}`);
  }

  const data = await res.json();

  // Return minimal shape — never echo text/html/subject back.
  return {
    ok: true,
    id: data.id,
    to,
    recipient_domain: derivedDomain,
  };
}

/**
 * Look up the delivery status of a previously-sent email.
 *
 * Required input fields:
 *   - email_id: Resend email ID returned by emailSend
 *
 * Returns: { id, to, from, subject, last_event, created_at }
 *
 * @param {object} input
 * @param {{ agent_id?: string }} options
 */
export async function emailStatusLookup(input = {}, options = {}) {
  const emailId = String(input.email_id || '').trim();
  if (!emailId) throw new Error('"email_id" is required');

  const apiKey = requireCredential('RESEND_API_KEY', options);
  const agentId = options.agent_id ?? null;

  const wrappedFetch = wrapFetch('email_status_lookup', agentId);
  const res = await wrappedFetch(`${RESEND_API_BASE}/emails/${encodeURIComponent(emailId)}`, {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: 'application/json',
    },
  });

  if (!res.ok) {
    const message = await resendErrorMessage(res);
    throw new Error(`Resend API error (${res.status}): ${message}`);
  }

  const data = await res.json();

  return {
    id: data.id,
    to: data.to,
    from: data.from,
    subject: data.subject,
    last_event: data.last_event,
    created_at: data.created_at,
  };
}
