import { createDbService } from '@/server/services/db.js';
/**
 * PII helpers: decrypt user/vendor email via pgp_sym_decrypt.
 * Shared across handlers that need to resolve encrypted email addresses.
 */

import { eq, sql } from 'drizzle-orm';
import { users, vendors } from '../../../db/schema.js';
import { captureCaught } from '@/server/observability/capture.server';
import type { DispatchEnv } from '../types.js';

export async function decryptUserEmail(env: DispatchEnv, userId: string): Promise<string | null> {
  if (!env.PII_KEY) return null;
  const db = createDbService({ DATABASE_URL: env.DATABASE_URL });
  try {
    const rows = await db
      .select({
        email: sql<string>`pgp_sym_decrypt(CASE WHEN substring(${users.email}, 1, 2) = '\\x' THEN ${users.email}::bytea ELSE decode(${users.email}, 'base64') END, ${env.PII_KEY})::text`,
      })
      .from(users)
      .where(eq(users.id, userId))
      .limit(1);
    return rows[0]?.email ?? null;
  } catch (err) {
    captureCaught(err, { scope: 'server.workflows.outbox.decrypt-email', severity: 'warning' });
    return null;
  }
}

export async function decryptVendorEmail(
  env: DispatchEnv,
  vendorId: string,
): Promise<string | null> {
  if (!env.PII_KEY) return null;
  const db = createDbService({ DATABASE_URL: env.DATABASE_URL });
  try {
    const rows = await db
      .select({
        email: sql<string>`pgp_sym_decrypt(CASE WHEN substring(${vendors.email}, 1, 2) = '\\x' THEN ${vendors.email}::bytea ELSE decode(${vendors.email}, 'base64') END, ${env.PII_KEY})::text`,
      })
      .from(vendors)
      .where(eq(vendors.id, vendorId))
      .limit(1);
    return rows[0]?.email ?? null;
  } catch (err) {
    captureCaught(err, { scope: 'server.workflows.outbox.decrypt-email', severity: 'warning' });
    return null;
  }
}

// ─── Fail-loud recipient resolution (money-adjacent handlers) ───────────────────
// See docs/specs/2026-06-18-outbox-handler-failure-semantics-design.md.
//
// A money-adjacent handler (receipt/refund/return) must NOT clean-return when its
// recipient is unresolvable — that marks the outbox row processed-clean and the
// money email is silently dropped. These wrappers throw instead, so the consume
// paths' existing retry → dead-letter → outbox_abandoned pager engages.
//
// Skip taxonomy (the one distinction): "recipient exists but unresolvable" = THROW;
// "no recipient / secret-missing during deploy window (non-strict)" = documented
// skip (returns null, caller returns cleanly).

/**
 * Resolve a buyer's email for a money-adjacent handler, or fail loud.
 * - PII_KEY missing under non-strict → documented deploy-window skip → returns null.
 * - PII_KEY missing under strict → throws.
 * - Key present but decrypt/lookup yields no email → throws (recipient unresolvable).
 */
export async function requireDecryptedUserEmail(
  env: DispatchEnv,
  userId: string,
  eventType: string,
): Promise<string | null> {
  if (!env.PII_KEY) {
    if (env.strict !== false) {
      throw new Error(`[outbox] PII_KEY not configured for ${eventType}`);
    }
    console.warn(JSON.stringify({ event: 'outbox_secret_missing', secret: 'PII_KEY', eventType }));
    return null;
  }
  const email = await decryptUserEmail(env, userId);
  if (!email) {
    throw new Error(`[outbox] recipient email unresolvable for ${eventType} (userId ${userId})`);
  }
  return email;
}

/**
 * Vendor-recipient twin of {@link requireDecryptedUserEmail}. Same contract.
 */
export async function requireDecryptedVendorEmail(
  env: DispatchEnv,
  vendorId: string,
  eventType: string,
): Promise<string | null> {
  if (!env.PII_KEY) {
    if (env.strict !== false) {
      throw new Error(`[outbox] PII_KEY not configured for ${eventType}`);
    }
    console.warn(JSON.stringify({ event: 'outbox_secret_missing', secret: 'PII_KEY', eventType }));
    return null;
  }
  const email = await decryptVendorEmail(env, vendorId);
  if (!email) {
    throw new Error(
      `[outbox] vendor recipient email unresolvable for ${eventType} (vendorId ${vendorId})`,
    );
  }
  return email;
}
