/**
 * Per-domain branding overrides: schema, parsing, host extraction, and match.
 *
 * The webmail can be served on multiple hostnames (e.g. mail1.example.com,
 * mail2.other.com). Each hostname can override a subset of branding fields;
 * unset fields fall back to the global admin/env/default value.
 */

import type { NextRequest } from 'next/server';

/** Config keys that can be overridden per domain. */
export const BRANDING_OVERRIDE_KEYS = [
  'appName',
  'appShortName',
  'appDescription',
  'faviconUrl',
  'pwaIconUrl',
  'pwaScreenshotMobileUrl',
  'pwaScreenshotDesktopUrl',
  'pwaThemeColor',
  'pwaBackgroundColor',
  'appLogoLightUrl',
  'appLogoDarkUrl',
  'loginLogoLightUrl',
  'loginLogoDarkUrl',
  'loginCompanyName',
  'loginImprintUrl',
  'loginPrivacyPolicyUrl',
  'loginWebsiteUrl',
] as const;

export type BrandingOverrideKey = (typeof BRANDING_OVERRIDE_KEYS)[number];

export interface DomainBrandingEntry {
  /**
   * Hostname this entry applies to. Either an exact host like
   * "mail.example.com" or a wildcard like "*.example.com" (matches any
   * direct or deeper subdomain). Case-insensitive; trailing dots are
   * stripped on parse.
   */
  host: string;
  appName?: string;
  appShortName?: string;
  appDescription?: string;
  faviconUrl?: string;
  pwaIconUrl?: string;
  pwaScreenshotMobileUrl?: string;
  pwaScreenshotDesktopUrl?: string;
  pwaThemeColor?: string;
  pwaBackgroundColor?: string;
  appLogoLightUrl?: string;
  appLogoDarkUrl?: string;
  loginLogoLightUrl?: string;
  loginLogoDarkUrl?: string;
  loginCompanyName?: string;
  loginImprintUrl?: string;
  loginPrivacyPolicyUrl?: string;
  loginWebsiteUrl?: string;
}

// Accepts plain hostnames (foo, foo.bar, foo.bar.baz) and one-level wildcards
// at the leftmost label (*.example.com). Rejects IPs, scheme/path/userinfo,
// and embedded wildcards.
const HOST_RE = /^(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;

function normalizeHost(host: string): string {
  return host.trim().toLowerCase().replace(/\.+$/, '');
}

/** Parse the raw config value (array of entries, or string-JSON). Invalid entries are dropped. */
export function parseDomainBranding(raw: unknown): DomainBrandingEntry[] {
  if (!raw) return [];
  let value = raw;
  if (typeof value === 'string') {
    if (!value.trim()) return [];
    try {
      value = JSON.parse(value);
    } catch {
      return [];
    }
  }
  if (!Array.isArray(value)) return [];

  const seen = new Set<string>();
  const out: DomainBrandingEntry[] = [];
  for (const item of value) {
    if (!item || typeof item !== 'object') continue;
    const rec = item as Record<string, unknown>;
    const rawHost = typeof rec.host === 'string' ? rec.host : '';
    const host = normalizeHost(rawHost);
    if (!host || !HOST_RE.test(host)) continue;
    if (seen.has(host)) continue;
    seen.add(host);

    const entry: DomainBrandingEntry = { host };
    const writable = entry as unknown as Record<string, string>;
    for (const key of BRANDING_OVERRIDE_KEYS) {
      const v = rec[key];
      if (typeof v === 'string' && v.length > 0) {
        writable[key] = v;
      }
    }
    out.push(entry);
  }
  return out;
}

type HeadersLike = Headers | { get(name: string): string | null };

/**
 * Pick the request's host, preferring X-Forwarded-Host (first entry if
 * comma-separated) over Host. Strips the port. Returns null when no usable
 * host header is set.
 */
export function pickRequestHost(headersOrReq: NextRequest | HeadersLike): string | null {
  const headers: HeadersLike = 'headers' in headersOrReq ? (headersOrReq as NextRequest).headers : headersOrReq;
  const raw = headers.get('x-forwarded-host') || headers.get('host');
  if (!raw) return null;
  const first = raw.split(',')[0]?.trim();
  if (!first) return null;
  return normalizeHost(first.split(':')[0]);
}

/**
 * Find the entry whose host matches `host`. Exact match always wins; among
 * wildcards the longest (most-specific) suffix wins. Returns {} when no
 * entry matches.
 */
export function matchDomainBranding(
  host: string | null,
  entries: DomainBrandingEntry[],
): Partial<DomainBrandingEntry> {
  if (!host || entries.length === 0) return {};
  const lower = normalizeHost(host);
  let wildcardMatch: DomainBrandingEntry | undefined;
  for (const entry of entries) {
    if (entry.host === lower) return entry;
    if (entry.host.startsWith('*.')) {
      const suffix = entry.host.slice(1); // ".example.com"
      if (lower.endsWith(suffix) && lower.length > suffix.length) {
        if (!wildcardMatch || entry.host.length > wildcardMatch.host.length) {
          wildcardMatch = entry;
        }
      }
    }
  }
  return wildcardMatch ?? {};
}
