/**
 * Typed accessors for port-level configuration with env-fallback.
 *
 * Settings are stored in the `system_settings` table keyed by (key, portId).
 * The functions in this module resolve a port's effective configuration for
 * a given domain (email, Documenso, branding, reminders) by reading the
 * port-scoped row first, falling back to the global row, and finally to the
 * env var when neither is set.
 */
import { env } from '@/lib/env';
import { getSetting } from '@/lib/services/settings.service';

// ─── Setting key constants ───────────────────────────────────────────────────

export const SETTING_KEYS = {
  // Email
  emailFromName: 'email_from_name',
  emailFromAddress: 'email_from_address',
  emailReplyTo: 'email_reply_to',
  // email_signature_html / email_footer_html — removed; the email shell
  // reads branding_email_header_html / branding_email_footer_html from
  // /admin/branding, which is the source of truth.
  emailAllowPersonalAccountSends: 'email_allow_personal_account_sends',
  smtpHostOverride: 'smtp_host_override',
  smtpPortOverride: 'smtp_port_override',
  smtpUserOverride: 'smtp_user_override',
  smtpPassOverride: 'smtp_pass_override',

  // Documenso / EOI
  documensoApiUrlOverride: 'documenso_api_url_override',
  documensoApiKeyOverride: 'documenso_api_key_override',
  documensoApiVersionOverride: 'documenso_api_version_override',
  documensoEoiTemplateId: 'documenso_eoi_template_id',
  // Documenso template recipient slot IDs are per-Documenso-instance
  // numeric values, so they have to follow the per-port template config.
  // Falling back to env keeps single-tenant deploys working.
  documensoClientRecipientId: 'documenso_client_recipient_id',
  documensoDeveloperRecipientId: 'documenso_developer_recipient_id',
  documensoApprovalRecipientId: 'documenso_approval_recipient_id',
  // Per-port Documenso webhook secret — two ports pointed at different
  // Documenso instances cannot share the global env secret. The receiver
  // resolves the matching port by trying each enabled secret with a
  // timing-safe comparison.
  documensoWebhookSecret: 'documenso_webhook_secret',
  eoiDefaultPathway: 'eoi_default_pathway',
  // Identity of the developer + approver that the template's static
  // recipient slots get filled with. Old system hardcoded these
  // (David Mizrahi, Abbie May @ portnimara.com) but multi-port deploys
  // need per-port values. Falls back to env or "" if neither set.
  documensoDeveloperName: 'documenso_developer_name',
  documensoDeveloperEmail: 'documenso_developer_email',
  documensoApproverName: 'documenso_approver_name',
  documensoApproverEmail: 'documenso_approver_email',
  // Optional CRM-user binding for the developer + approver slots.
  // When set, the per-port admin UI shows "Linked to <user>" and
  // the webhook handler can match the Documenso developer signer
  // against this user's email for in-CRM signing-status updates.
  // Plan Phase 7 (Project Director RBAC). Stored as the user.id.
  documensoDeveloperUserId: 'documenso_developer_user_id',
  documensoApproverUserId: 'documenso_approver_user_id',
  // Display labels for the developer + approver slots, used in
  // email subjects + signer-progress UI ("Your Project Director,
  // Marie, has signed…"). Defaults to "Developer" / "Approver".
  documensoDeveloperLabel: 'documenso_developer_label',
  documensoApproverLabel: 'documenso_approver_label',
  // Sending behavior for the initial "please sign" invitation email
  // after a document is generated. 'auto' = our branded email goes
  // out immediately; 'manual' = doc generated, signing URL shown in
  // UI, rep clicks a Send button to dispatch. Per-port so different
  // ports can default to different rep workflows.
  eoiSendMode: 'eoi_send_mode',
  // Public-facing host where embedded signing pages live. Used to
  // transform raw Documenso signing URLs into branded
  // {host}/sign/<type>/<token> URLs that go in our outbound emails.
  // Falls back to APP_URL when unset.
  embeddedSigningHost: 'embedded_signing_host',
  // Documenso template IDs for contract / reservation if the port
  // uses templates rather than per-deal uploads. Optional.
  documensoContractTemplateId: 'documenso_contract_template_id',
  documensoReservationTemplateId: 'documenso_reservation_template_id',
  // v2-only: PARALLEL (default) or SEQUENTIAL signing-order enforcement on
  // multi-recipient envelopes. When SEQUENTIAL is set + apiVersion=v2,
  // Documenso refuses to email recipient N+1 until recipient N has signed.
  // Ignored entirely on v1 instances.
  documensoSigningOrder: 'documenso_signing_order',
  // v2-only override of the post-signing redirect URL set on documentMeta.
  // Falls back to the embedded signing host (or APP_URL) when unset. Use
  // this to land signed clients on /portal/eoi-complete (or wherever
  // makes sense for the workflow).
  documensoRedirectUrl: 'documenso_redirect_url',

  // Branding
  brandingLogoUrl: 'branding_logo_url',
  brandingPrimaryColor: 'branding_primary_color',
  brandingAppName: 'branding_app_name',
  brandingEmailHeaderHtml: 'branding_email_header_html',
  brandingEmailFooterHtml: 'branding_email_footer_html',

  // Reminders (port-level defaults)
  reminderDefaultDays: 'reminder_default_days',
  reminderDefaultEnabled: 'reminder_default_enabled',
  reminderDigestEnabled: 'reminder_digest_enabled',
  reminderDigestTime: 'reminder_digest_time',
  reminderDigestTimezone: 'reminder_digest_timezone',

  // Berths
  berthsDefaultCurrency: 'berths_default_currency',
} as const;

// ─── Helper ──────────────────────────────────────────────────────────────────

async function readSetting<T>(key: string, portId: string): Promise<T | null> {
  const setting = await getSetting(key, portId);
  if (!setting) return null;
  return setting.value as T;
}

// ─── Email ──────────────────────────────────────────────────────────────────

export interface PortEmailConfig {
  fromName: string;
  fromAddress: string;
  replyTo: string | null;
  smtpHost: string;
  smtpPort: number;
  smtpUser: string | null;
  smtpPass: string | null;
  /**
   * When false, only the system (port-config) sender identity is allowed.
   * When true, admins/users may send via their connected personal email
   * account. Defaults to false for safety.
   */
  allowPersonalAccountSends: boolean;
}

export async function getPortEmailConfig(portId: string): Promise<PortEmailConfig> {
  const [
    fromName,
    fromAddress,
    replyTo,
    smtpHost,
    smtpPort,
    smtpUser,
    smtpPass,
    allowPersonalAccountSends,
  ] = await Promise.all([
    readSetting<string>(SETTING_KEYS.emailFromName, portId),
    readSetting<string>(SETTING_KEYS.emailFromAddress, portId),
    readSetting<string>(SETTING_KEYS.emailReplyTo, portId),
    readSetting<string>(SETTING_KEYS.smtpHostOverride, portId),
    readSetting<number>(SETTING_KEYS.smtpPortOverride, portId),
    readSetting<string>(SETTING_KEYS.smtpUserOverride, portId),
    readSetting<string>(SETTING_KEYS.smtpPassOverride, portId),
    readSetting<boolean>(SETTING_KEYS.emailAllowPersonalAccountSends, portId),
  ]);

  // Parse env.SMTP_FROM into name + address if no port override
  let envFromName = 'Port Nimara CRM';
  let envFromAddress = `noreply@${env.SMTP_HOST}`;
  if (env.SMTP_FROM) {
    const match = env.SMTP_FROM.match(/^(.+?)\s*<(.+)>$/);
    if (match) {
      envFromName = match[1]!.trim();
      envFromAddress = match[2]!.trim();
    } else {
      envFromAddress = env.SMTP_FROM;
    }
  }

  return {
    fromName: fromName ?? envFromName,
    fromAddress: fromAddress ?? envFromAddress,
    replyTo: replyTo ?? null,
    smtpHost: smtpHost ?? env.SMTP_HOST,
    smtpPort: smtpPort ?? env.SMTP_PORT,
    smtpUser: smtpUser ?? env.SMTP_USER ?? null,
    smtpPass: smtpPass ?? env.SMTP_PASS ?? null,
    allowPersonalAccountSends: allowPersonalAccountSends ?? false,
  };
}

// ─── Documenso ──────────────────────────────────────────────────────────────

export type EoiPathway = 'documenso-template' | 'inapp';
export type DocumensoApiVersion = 'v1' | 'v2';
export type EoiSendMode = 'auto' | 'manual';

export interface PortDocumensoConfig {
  apiUrl: string;
  apiKey: string;
  apiVersion: DocumensoApiVersion;
  eoiTemplateId: number;
  defaultPathway: EoiPathway;
  /** Documenso template recipient slot IDs (per-instance numeric). */
  clientRecipientId: number;
  developerRecipientId: number;
  approvalRecipientId: number;
  /** Static developer + approver identity per port (was hardcoded in old system). */
  developerName: string;
  developerEmail: string;
  approverName: string;
  approverEmail: string;
  /**
   * Auto = system sends our branded "please sign" email immediately
   * after generation. Manual = generates only; rep clicks a separate
   * Send button. Defaults to 'manual' to match the old system's
   * behavior (which also doesn't auto-send).
   */
  sendMode: EoiSendMode;
  /**
   * Host that wraps Documenso signing URLs into branded embed URLs.
   * Outbound emails point here for the actual sign UI. e.g.
   * `https://portnimara.com` makes sign URLs look like
   * `https://portnimara.com/sign/<type>/<token>`.
   */
  embeddedSigningHost: string | null;
  /** Optional template IDs for contract / reservation. null = use
   *  upload-and-place-fields per deal instead of templates. */
  contractTemplateId: number | null;
  reservationTemplateId: number | null;
  /** Per-port display labels for the developer + approver slots — drive
   *  email subjects and signer-progress UI copy. */
  developerLabel: string;
  approverLabel: string;
  /** Optional CRM-user binding for the developer / approver slots.
   *  When set, the per-port admin UI auto-fills name/email from the
   *  user's profile and the webhook handler matches against this
   *  user's email for in-CRM signing-status updates. */
  developerUserId: string | null;
  approverUserId: string | null;
  /**
   * v2-only: PARALLEL (default) or SEQUENTIAL signing-order enforcement.
   * `null` keeps the upstream default (PARALLEL); a non-null value gets
   * passed verbatim. v1 instances ignore this — see admin Documenso page.
   */
  signingOrder: 'PARALLEL' | 'SEQUENTIAL' | null;
  /**
   * v2-only: post-signing redirect URL set on documentMeta. When null,
   * the upstream Documenso default applies (Documenso's own thank-you
   * page). Typically set to `{embeddedSigningHost}/sign/success` so
   * signers land back on the branded marketing site.
   */
  redirectUrl: string | null;
}

function toIntOrNull(raw: unknown): number | null {
  if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
  if (typeof raw === 'string' && raw.trim()) {
    const n = Number(raw);
    return Number.isFinite(n) ? n : null;
  }
  return null;
}

export async function getPortDocumensoConfig(portId: string): Promise<PortDocumensoConfig> {
  const [
    apiUrl,
    apiKey,
    apiVersion,
    eoiTemplateId,
    clientRecipientId,
    developerRecipientId,
    approvalRecipientId,
    defaultPathway,
    developerName,
    developerEmail,
    approverName,
    approverEmail,
    sendMode,
    embeddedSigningHost,
    contractTemplateId,
    reservationTemplateId,
    developerLabel,
    approverLabel,
    developerUserId,
    approverUserId,
    signingOrder,
    redirectUrlOverride,
  ] = await Promise.all([
    readSetting<string>(SETTING_KEYS.documensoApiUrlOverride, portId),
    readSetting<string>(SETTING_KEYS.documensoApiKeyOverride, portId),
    readSetting<DocumensoApiVersion>(SETTING_KEYS.documensoApiVersionOverride, portId),
    readSetting<string | number>(SETTING_KEYS.documensoEoiTemplateId, portId),
    readSetting<string | number>(SETTING_KEYS.documensoClientRecipientId, portId),
    readSetting<string | number>(SETTING_KEYS.documensoDeveloperRecipientId, portId),
    readSetting<string | number>(SETTING_KEYS.documensoApprovalRecipientId, portId),
    readSetting<EoiPathway>(SETTING_KEYS.eoiDefaultPathway, portId),
    readSetting<string>(SETTING_KEYS.documensoDeveloperName, portId),
    readSetting<string>(SETTING_KEYS.documensoDeveloperEmail, portId),
    readSetting<string>(SETTING_KEYS.documensoApproverName, portId),
    readSetting<string>(SETTING_KEYS.documensoApproverEmail, portId),
    readSetting<EoiSendMode>(SETTING_KEYS.eoiSendMode, portId),
    readSetting<string>(SETTING_KEYS.embeddedSigningHost, portId),
    readSetting<string | number>(SETTING_KEYS.documensoContractTemplateId, portId),
    readSetting<string | number>(SETTING_KEYS.documensoReservationTemplateId, portId),
    readSetting<string>(SETTING_KEYS.documensoDeveloperLabel, portId),
    readSetting<string>(SETTING_KEYS.documensoApproverLabel, portId),
    readSetting<string>(SETTING_KEYS.documensoDeveloperUserId, portId),
    readSetting<string>(SETTING_KEYS.documensoApproverUserId, portId),
    readSetting<'PARALLEL' | 'SEQUENTIAL'>(SETTING_KEYS.documensoSigningOrder, portId),
    readSetting<string>(SETTING_KEYS.documensoRedirectUrl, portId),
  ]);

  return {
    apiUrl: apiUrl ?? env.DOCUMENSO_API_URL,
    apiKey: apiKey ?? env.DOCUMENSO_API_KEY,
    apiVersion: apiVersion ?? env.DOCUMENSO_API_VERSION,
    eoiTemplateId: toIntOrNull(eoiTemplateId) ?? env.DOCUMENSO_TEMPLATE_ID_EOI,
    clientRecipientId: toIntOrNull(clientRecipientId) ?? env.DOCUMENSO_CLIENT_RECIPIENT_ID,
    developerRecipientId: toIntOrNull(developerRecipientId) ?? env.DOCUMENSO_DEVELOPER_RECIPIENT_ID,
    approvalRecipientId: toIntOrNull(approvalRecipientId) ?? env.DOCUMENSO_APPROVAL_RECIPIENT_ID,
    defaultPathway: defaultPathway ?? 'documenso-template',
    developerName: developerName ?? '',
    developerEmail: developerEmail ?? '',
    approverName: approverName ?? '',
    approverEmail: approverEmail ?? '',
    sendMode: sendMode ?? 'manual',
    embeddedSigningHost: embeddedSigningHost ?? null,
    contractTemplateId: toIntOrNull(contractTemplateId),
    reservationTemplateId: toIntOrNull(reservationTemplateId),
    developerLabel: developerLabel ?? 'Developer',
    approverLabel: approverLabel ?? 'Approver',
    developerUserId: developerUserId ?? null,
    approverUserId: approverUserId ?? null,
    signingOrder: signingOrder ?? null,
    redirectUrl: redirectUrlOverride ?? null,
  };
}

/**
 * List every (portId, webhookSecret) pair configured across the platform,
 * plus a wildcard-port entry for the global env secret. The Documenso
 * webhook receiver iterates the list with `timingSafeEqual` until it
 * finds a match, then dispatches with the resolved portId.
 *
 * `null` portId in the returned array means "matches but no port was
 * resolved" — the caller falls back to the legacy global path.
 */
export interface DocumensoSecretEntry {
  portId: string | null;
  secret: string;
}

export async function listDocumensoWebhookSecrets(): Promise<DocumensoSecretEntry[]> {
  const { db } = await import('@/lib/db');
  const { systemSettings } = await import('@/lib/db/schema/system');
  const { eq, isNotNull } = await import('drizzle-orm');
  const rows = await db
    .select({ portId: systemSettings.portId, value: systemSettings.value })
    .from(systemSettings)
    .where(eq(systemSettings.key, SETTING_KEYS.documensoWebhookSecret));
  void isNotNull; // imported for future filters
  const out: DocumensoSecretEntry[] = [];
  for (const row of rows) {
    if (typeof row.value !== 'string' || !row.value || !row.portId) continue;
    out.push({ portId: row.portId, secret: row.value });
  }
  // Append the global env secret as a fallback ONLY when it's a real,
  // non-empty value. An empty env secret would otherwise match an empty
  // X-Documenso-Secret header (verifyDocumensoSecret guards this too,
  // but skipping the entry here keeps the matched-secret loop honest).
  if (env.DOCUMENSO_WEBHOOK_SECRET) {
    out.push({ portId: null, secret: env.DOCUMENSO_WEBHOOK_SECRET });
  }
  return out;
}

// ─── Branding ───────────────────────────────────────────────────────────────

/**
 * NOT YET WIRED end-to-end. The `/admin/branding` page persists these
 * settings to system_settings, but the email templates in
 * `src/lib/email/templates/` and the `<BrandedAuthShell>` component in
 * `src/components/shared/branded-auth-shell.tsx` still hardcode the
 * `s3.portnimara.com` logo URL and the Port Nimara color palette. A
 * second port wired into this CRM will see Port Nimara branding in
 * every transactional email until those consumers call
 * `getPortBrandingConfig(portId)`. Tracked as audit finding R2-H15.
 *
 * To wire fully:
 *   1. Take `branding` config as a server-side prop into
 *      `<BrandedAuthShell>` (pass it from the page server component).
 *   2. Refactor the email shell helper in each `templates/*.ts` module
 *      to take `headerHtml` / `footerHtml` / `primaryColor` instead of
 *      the inline constants.
 *   3. In each sender, call `getPortBrandingConfig(portId)` and thread
 *      the branding values into the template call.
 */
export interface PortBrandingConfig {
  logoUrl: string | null;
  primaryColor: string;
  appName: string;
  emailHeaderHtml: string | null;
  emailFooterHtml: string | null;
}

const DEFAULT_BRANDING: PortBrandingConfig = {
  logoUrl: null,
  primaryColor: '#1e293b',
  appName: 'Port Nimara CRM',
  emailHeaderHtml: null,
  emailFooterHtml: null,
};

export async function getPortBrandingConfig(portId: string): Promise<PortBrandingConfig> {
  const [logoUrl, primaryColor, appName, emailHeaderHtml, emailFooterHtml] = await Promise.all([
    readSetting<string>(SETTING_KEYS.brandingLogoUrl, portId),
    readSetting<string>(SETTING_KEYS.brandingPrimaryColor, portId),
    readSetting<string>(SETTING_KEYS.brandingAppName, portId),
    readSetting<string>(SETTING_KEYS.brandingEmailHeaderHtml, portId),
    readSetting<string>(SETTING_KEYS.brandingEmailFooterHtml, portId),
  ]);

  return {
    logoUrl: logoUrl ?? DEFAULT_BRANDING.logoUrl,
    primaryColor: primaryColor ?? DEFAULT_BRANDING.primaryColor,
    appName: appName ?? DEFAULT_BRANDING.appName,
    emailHeaderHtml: emailHeaderHtml ?? DEFAULT_BRANDING.emailHeaderHtml,
    emailFooterHtml: emailFooterHtml ?? DEFAULT_BRANDING.emailFooterHtml,
  };
}

// ─── Reminders ──────────────────────────────────────────────────────────────

export interface PortReminderConfig {
  defaultDays: number;
  defaultEnabled: boolean;
  digestEnabled: boolean;
  digestTime: string; // 'HH:MM'
  digestTimezone: string;
}

const DEFAULT_REMINDER: PortReminderConfig = {
  defaultDays: 7,
  defaultEnabled: false,
  digestEnabled: false,
  digestTime: '09:00',
  digestTimezone: 'Europe/Warsaw',
};

/**
 * Port-level default currency for newly-created berths. Per-berth
 * `priceCurrency` overrides this when set. Defaults to USD because
 * 95% of marinas in the rollout target are USD-denominated.
 */
export async function getPortBerthsDefaultCurrency(portId: string): Promise<string> {
  const value = await readSetting<string>(SETTING_KEYS.berthsDefaultCurrency, portId);
  return (value ?? 'USD').trim().toUpperCase() || 'USD';
}

export async function getPortReminderConfig(portId: string): Promise<PortReminderConfig> {
  const [defaultDays, defaultEnabled, digestEnabled, digestTime, digestTimezone] =
    await Promise.all([
      readSetting<number>(SETTING_KEYS.reminderDefaultDays, portId),
      readSetting<boolean>(SETTING_KEYS.reminderDefaultEnabled, portId),
      readSetting<boolean>(SETTING_KEYS.reminderDigestEnabled, portId),
      readSetting<string>(SETTING_KEYS.reminderDigestTime, portId),
      readSetting<string>(SETTING_KEYS.reminderDigestTimezone, portId),
    ]);

  return {
    defaultDays: defaultDays ?? DEFAULT_REMINDER.defaultDays,
    defaultEnabled: defaultEnabled ?? DEFAULT_REMINDER.defaultEnabled,
    digestEnabled: digestEnabled ?? DEFAULT_REMINDER.digestEnabled,
    digestTime: digestTime ?? DEFAULT_REMINDER.digestTime,
    digestTimezone: digestTimezone ?? DEFAULT_REMINDER.digestTimezone,
  };
}
