import { and, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { systemSettings } from '@/lib/db/schema/system';
import { settingKeyForSubject, type TemplateKey } from '@/lib/email/template-catalog';

/**
 * Returns the per-port subject override for a transactional email template,
 * or null if no override is configured.
 *
 * Per-port row wins over global (null portId). String values are returned
 * as-is; non-string values are ignored (treated as no override).
 */
export async function loadSubjectOverride(
  portId: string,
  key: TemplateKey,
): Promise<string | null> {
  const settingKey = settingKeyForSubject(key);
  const rows = await db
    .select({ value: systemSettings.value, portId: systemSettings.portId })
    .from(systemSettings)
    .where(eq(systemSettings.key, settingKey));

  // Prefer per-port row; fall back to global (null portId).
  const portRow = rows.find((r) => r.portId === portId);
  const globalRow = rows.find((r) => r.portId === null);
  const value = portRow?.value ?? globalRow?.value ?? null;
  return typeof value === 'string' && value.trim() ? value : null;
}

/**
 * Synchronous helper for substituting {{token}} placeholders in an email
 * subject line.
 *
 * Defensive CRLF neutralization (audit L16c): a subject is an email *header*,
 * and a CR/LF inside a substituted token value is the classic header-injection
 * primitive (smuggle a `Bcc:` / second header / a fake body). nodemailer
 * already strips CR/LF from header values before transmission, so this is not
 * exploitable in practice through our send path - but a token value can flow
 * from user-controlled data (client name, berth label, …), so we strip
 * CR/LF (and the rest of the C0/DEL control range) from each substituted value
 * here too, in depth, rather than relying solely on the transport. The static
 * template text is admin-authored and left untouched.
 */
export function applySubjectTokens(
  template: string,
  tokens: Record<string, string | number | undefined>,
): string {
  return template.replace(/\{\{(\w+)\}\}/g, (match, name: string) => {
    const v = tokens[name];
    if (v === undefined || v === null) return match;
    // Replace CR/LF (and the rest of the C0/DEL control range) with a single
    // space so a multi-line token value can never break the subject onto a
    // new header line.

    return String(v).replace(/[\x00-\x1f\x7f]+/g, ' ');
  });
}

// Suppress unused-import lint when the helper is not yet referenced from
// every template - every consumer uses `and` once it integrates.
void and;
