/**
 * User preferences queries — system-i18n.
 *
 * Provides locale-aware read/write helpers for user_preferences rows.
 * Called by the PATCH /api/preferences route handler.
 */
import { and, eq } from 'drizzle-orm'
import type { Db } from '../client'
import { userPreferences } from '../schema/user-preferences'
import { tenants } from '../schema/tenants'
import { tenantSettings } from '../schema/tenants'
import { auditLog } from './_audit-forward'

/**
 * Returns the locale preference for a user within a tenant.
 * Returns null if no preference row exists.
 */
export async function getUserLocalePreference(
  db: Db,
  userId: string,
  tenantId: string,
): Promise<string | null> {
  const [row] = await db
    .select({ locale: userPreferences.locale })
    .from(userPreferences)
    .where(
      and(eq(userPreferences.userId, userId), eq(userPreferences.tenantId, tenantId)),
    )
    .limit(1)
  return row?.locale ?? null
}

/**
 * Upserts the locale for a user within a tenant.
 * Creates the preferences row if it doesn't exist.
 */
export async function setUserLocale(
  db: Db,
  userId: string,
  tenantId: string,
  locale: string,
): Promise<void> {
  await db
    .insert(userPreferences)
    .values({ userId, tenantId, locale })
    .onConflictDoUpdate({
      target: [userPreferences.userId, userPreferences.tenantId],
      set: { locale },
    })
}

/**
 * Updates the tenant's country_code (admin-only operation).
 * The CHECK constraint in the DB enforces the supported country set.
 */
export async function setTenantCountryCode(
  db: Db,
  tenantId: string,
  countryCode: string,
): Promise<void> {
  await db
    .update(tenants)
    .set({ countryCode })
    .where(eq(tenants.id, tenantId))
}

export async function setTenantForceShell(db: Db, tenantId: string, actorId: string, forceShell: 'classic' | 'os' | null): Promise<void> {
  await db.transaction(async (tx) => {
    const [current] = await tx.select({ forceShell: tenantSettings.forceShell }).from(tenantSettings)
      .where(eq(tenantSettings.tenantId, tenantId)).limit(1)
    await tx.insert(tenantSettings).values({ tenantId, forceShell })
      .onConflictDoUpdate({ target: tenantSettings.tenantId, set: { forceShell } })
    await tx.insert(auditLog).values({ tenantId, actorId, actorType: 'user', entityType: 'tenant_settings', entityId: tenantId, action: 'tenant_settings.force_shell.updated', changes: { force_shell: [current?.forceShell ?? null, forceShell] } })
  })
}

// ── settings-module: account settings ────────────────────────────────────────

export interface TenantAccountSettings {
  name: string
  slug: string
  timezone: string
  logoUrl: string | null
}

/**
 * Returns the public account settings for a tenant.
 */
export async function getTenantAccountSettings(
  db: Db,
  tenantId: string,
): Promise<TenantAccountSettings | null> {
  const [row] = await db
    .select({
      name: tenants.name,
      slug: tenants.slug,
      timezone: tenants.defaultTimezone,
      logoUrl: tenants.logoUrl,
    })
    .from(tenants)
    .where(eq(tenants.id, tenantId))
    .limit(1)

  if (!row) return null
  return { ...row, logoUrl: row.logoUrl ?? null }
}

// ── settings-module: SMTP / white-label settings ─────────────────────────────

export interface SmtpSettings {
  host: string | null
  port: number | null
  username: string | null
  /** Encrypted blob — stored as { ciphertext, iv, authTag } JSON string */
  encryptedPassword: string | null
  fromName: string | null
  fromEmail: string | null
  domainVerified: boolean
  domainVerificationToken: string | null
  domainVerifiedAt: string | null
  replyTo: string | null
  tls: boolean
  smtpEncryption: 'tls' | 'starttls' | 'none'
  smtpFromOverride: string | null
  smtpEnabled: boolean
  smtpFallbackEnabled: boolean
  /** White-label: custom sending domain */
  customDomain: string | null
  /** White-label: hex/oklch brand color */
  brandColor: string | null
  /** White-label: logo URL */
  logoUrl: string | null
}

/**
 * Returns the SMTP + white-label settings stored in tenants.settings JSONB.
 * Returns defaults (all null) if none configured yet.
 */
export async function getSmtpSettings(
  db: Db,
  tenantId: string,
): Promise<SmtpSettings> {
  const [row] = await db
    .select({ settings: tenants.settings })
    .from(tenants)
    .where(eq(tenants.id, tenantId))
    .limit(1)

  const smtp = (row?.settings as Record<string, unknown> | null)?.smtp as Partial<SmtpSettings> | undefined
  return {
    host: smtp?.host ?? null,
    port: smtp?.port ?? null,
    username: smtp?.username ?? null,
    encryptedPassword: smtp?.encryptedPassword ?? null,
    fromName: smtp?.fromName ?? null,
    fromEmail: smtp?.fromEmail ?? null,
    domainVerified: smtp?.domainVerified ?? false,
    domainVerificationToken: smtp?.domainVerificationToken ?? null,
    domainVerifiedAt: smtp?.domainVerifiedAt ?? null,
    replyTo: smtp?.replyTo ?? null,
    tls: smtp?.tls ?? true,
    smtpEncryption: smtp?.smtpEncryption ?? (smtp?.tls === false ? 'none' : 'starttls'),
    smtpFromOverride: smtp?.smtpFromOverride ?? null,
    smtpEnabled: smtp?.smtpEnabled ?? false,
    smtpFallbackEnabled: smtp?.smtpFallbackEnabled ?? true,
    customDomain: smtp?.customDomain ?? null,
    brandColor: smtp?.brandColor ?? null,
    logoUrl: smtp?.logoUrl ?? null,
  }
}

/**
 * Updates SMTP + white-label settings in tenants.settings JSONB (merge patch).
 * Password must be pre-encrypted before calling.
 */
export async function updateSmtpSettings(
  db: Db,
  tenantId: string,
  actorId: string,
  patch: Partial<SmtpSettings>,
  opts?: { actorIp?: string | null; requestId?: string | null },
): Promise<void> {
  await db.transaction(async (tx) => {
    const [row] = await tx
      .select({ settings: tenants.settings })
      .from(tenants)
      .where(eq(tenants.id, tenantId))
      .limit(1)

    const current = (row?.settings ?? {}) as Record<string, unknown>
    const currentSmtp = (current.smtp ?? {}) as Record<string, unknown>

    await tx
      .update(tenants)
      .set({ settings: { ...current, smtp: { ...currentSmtp, ...patch } } })
      .where(eq(tenants.id, tenantId))

    await tx.insert(auditLog).values({
      tenantId,
      actorId,
      actorType: 'user',
      entityType: 'tenant',
      entityId: tenantId,
      action: 'smtp.configured',
      changes: {},
      ip: opts?.actorIp ?? null,
      requestId: opts?.requestId ?? null,
    })
  })
}

/**
 * Updates tenant account settings (name, timezone, logoUrl) in a transaction
 * with an audit log entry. Only the supplied fields are updated.
 */
export async function updateTenantAccountSettings(
  db: Db,
  tenantId: string,
  actorId: string,
  patch: { name?: string; timezone?: string; logoUrl?: string | null },
  opts?: { actorIp?: string | null; requestId?: string | null },
): Promise<void> {
  await db.transaction(async (tx) => {
    const set: Partial<typeof tenants.$inferInsert> = {}
    if (patch.name !== undefined) set.name = patch.name
    if (patch.timezone !== undefined) set.defaultTimezone = patch.timezone
    if (patch.logoUrl !== undefined) set.logoUrl = patch.logoUrl

    await tx.update(tenants).set(set).where(eq(tenants.id, tenantId))

    const changes: Record<string, [unknown, unknown]> = {}
    if (patch.name !== undefined) changes['name'] = [null, patch.name]
    if (patch.timezone !== undefined) changes['timezone'] = [null, patch.timezone]
    if (patch.logoUrl !== undefined) changes['logo_url'] = [null, patch.logoUrl]

    await tx.insert(auditLog).values({
      tenantId,
      actorId,
      actorType: 'user',
      entityType: 'tenant',
      entityId: tenantId,
      action: 'tenant.account_settings.updated',
      changes,
      ip: opts?.actorIp ?? null,
      requestId: opts?.requestId ?? null,
    })
  })
}
