// apps/web/src/server/referrals/settings.ts
import { eq } from 'drizzle-orm';
import { referralSettings } from '@/server/db/schema';
import type { DrizzleClient, TxDrizzleClient } from '@/server/db/client';
import {
  recordAffiliateAdminAction,
  updateReferralSettingsRow,
} from '@/server/db/queries/referrals/referral-writes';

export type AdapterOverride = {
  enabled: boolean;
  action?: 'pass' | 'flag' | 'hold' | 'block';
  params?: Record<string, unknown>;
};

export type ReferralSettings = {
  affiliatePct: number;
  // Tier commission rates — % of SALE directly (WYSIWYG: 3 = "3% of purchase amount")
  tier1Pct: number; // default 3
  tier2Pct: number; // default 4
  tier3Pct: number; // default 5
  tier2MinSales: number; // trailing-30-day purchase count to reach T2 (default 20)
  tier3MinSales: number; // trailing-30-day purchase count to reach T3 (default 50)
  referralPct: number; // % of sale → store credit for referrer (default 2)
  affiliateWindowDays: number;
  affiliateMaxOrders: number;
  cookieDays: number;
  rewardAgorot: number;
  refereeDiscountAgorot: number;
  holdDays: number;
  withdrawalMinAgorot: number;
  autoApproveLifetimeAgorot: number;
  brandKeywordBlocklist: string[];
  disallowedRefererHosts: string[];
  tosVersion: string;
  disputeWindowDays: number;
  reservePct: number;
  reserveReleaseDays: number;
  fraudConfig: Record<string, AdapterOverride>;
};

let _settingsCache: { value: ReferralSettings; expiresAt: number } | null = null;
const SETTINGS_TTL_MS = 30_000;

/**
 * Load referral settings from the single-row config table.
 * Throws if row id=1 is absent (never happens in prod; only during cold migration).
 * Results are cached per-isolate for 30s.
 */
export async function getReferralSettings(db: DrizzleClient): Promise<ReferralSettings> {
  if (_settingsCache && _settingsCache.expiresAt > Date.now()) {
    return _settingsCache.value;
  }

  const rows = await db.select().from(referralSettings).where(eq(referralSettings.id, 1)).limit(1);

  if (rows.length === 0) {
    throw new Error('referral_settings row not found — run migration seed');
  }

  const row = rows[0]!;
  const value: ReferralSettings = {
    affiliatePct: row.affiliatePct,
    tier1Pct: row.tier1Pct,
    tier2Pct: row.tier2Pct,
    tier3Pct: row.tier3Pct,
    tier2MinSales: row.tier2MinSales,
    tier3MinSales: row.tier3MinSales,
    referralPct: row.referralPct,
    affiliateWindowDays: row.affiliateWindowDays,
    affiliateMaxOrders: row.affiliateMaxOrders,
    cookieDays: row.cookieDays,
    rewardAgorot: row.rewardAgorot,
    refereeDiscountAgorot: row.refereeDiscountAgorot,
    holdDays: row.holdDays,
    withdrawalMinAgorot: row.withdrawalMinAgorot,
    autoApproveLifetimeAgorot: row.autoApproveLifetimeAgorot,
    brandKeywordBlocklist: row.brandKeywordBlocklist,
    disallowedRefererHosts: row.disallowedRefererHosts,
    tosVersion: row.tosVersion,
    disputeWindowDays: row.disputeWindowDays ?? 120,
    reservePct: row.reservePct ?? 0,
    reserveReleaseDays: row.reserveReleaseDays ?? 120,
    fraudConfig: (row.fraudConfig as Record<string, AdapterOverride>) ?? {},
  };

  _settingsCache = { value, expiresAt: Date.now() + SETTINGS_TTL_MS };
  return value;
}

/**
 * Invalidate the in-process settings cache so the next read fetches fresh data.
 */
export function invalidateSettingsCache(): void {
  _settingsCache = null;
}

/**
 * Update referral settings (admin only). Caller must verify admin role.
 * Wraps read + update + audit-insert in a single transaction so they are atomic.
 * md-afill §8: every settings mutation writes an affiliate_admin_actions audit row.
 */
export async function updateReferralSettings(
  db: TxDrizzleClient,
  patch: Partial<Omit<ReferralSettings, never>>,
  adminUserId: string,
): Promise<void> {
  await db.transaction(async (tx) => {
    // Snapshot current settings BEFORE the update (before payload for audit).
    const beforeRows = await tx
      .select()
      .from(referralSettings)
      .where(eq(referralSettings.id, 1))
      .limit(1);
    const beforeRow = beforeRows[0] ?? null;

    // Apply the patch.
    await updateReferralSettingsRow(tx, adminUserId, patch);

    // Write audit trail row (target_user_id is NULL — settings are global, not per-user).
    await recordAffiliateAdminAction(tx, {
      adminUserId,
      targetUserId: null,
      action: 'update_settings',
      payload: {
        before: beforeRow ?? {},
        after: patch,
      },
      reason: 'admin fraud/settings update',
    });
  });

  invalidateSettingsCache();
}
