/**
 * attributeRefereeOnSignup
 *
 * Post-registration side-effect: bind the md_ref cookie to the new account,
 * then mint a single-use platform-funded "welcome discount" promo code for
 * the referee.
 *
 * Semantics:
 *  - ONLY fires when the md_ref cookie is present (set by /api/referrals/touch).
 *  - Failure NEVER blocks signup — all errors are caught, logged, and swallowed.
 *  - Clears the md_ref cookie unconditionally once reached (one-shot).
 *
 * Call this AFTER the new user row is committed and BEFORE returning the
 * response.  Pass `responseHeaders` so the cookie-clear Set-Cookie header can
 * be appended.
 */

import type { DrizzleClient } from '../db/client.js';
import { bindReferralOnSignup } from './attribution.js';
import { ReferralConfigSchema, type CloudflareEnv } from '../env.js';
import { captureCaught } from '@/lib/observability';
import type { PromoRules } from '../promo/types.js';
import { isHttpsRequest } from '../auth/cookies.js';
import { getUserWithDecryptedEmail } from '../db/queries/users.js';
import {
  attachWelcomePromo,
  createWelcomePromo,
  updateRefereeEmailIndex,
} from '@/server/db/queries/referrals/referral-writes.js';
import { canonicalizeEmail } from './fraud/email-canonical-fn.js';

/**
 * Parse the md_ref cookie value from the Cookie header, or return null.
 */
function readMdRefCookie(request: Request): { linkId: string; clickedAt: Date | null } | null {
  const cookieHeader = request.headers.get('cookie') ?? '';
  const segment = cookieHeader
    .split(';')
    .map((c) => c.trim())
    .find((c) => c.startsWith('md_ref='));
  if (!segment) return null;
  const value = segment.slice('md_ref='.length).trim();
  if (value.length === 0) return null;
  const [linkId, tsRaw] = value.split(':');
  if (!linkId || linkId.length === 0) return null;
  const ts = Number(tsRaw);
  const clickedAt = tsRaw && Number.isFinite(ts) ? new Date(ts * 1000) : null;
  return { linkId, clickedAt };
}

/**
 * Generate a unique welcome-discount code with enough entropy to avoid
 * collisions (10 random uppercase alphanumeric chars ≈ 36^10 ≈ 3.7 × 10^15
 * space — negligible collision probability).
 */
function generateWelcomeCode(): string {
  const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  const bytes = crypto.getRandomValues(new Uint8Array(10));
  const suffix = Array.from(bytes)
    .map((b) => chars[b % chars.length])
    .join('');
  return `WELCOME-${suffix}`;
}

export async function attributeRefereeOnSignup(
  db: DrizzleClient,
  env: CloudflareEnv,
  request: Request,
  responseHeaders: Headers,
  refereeUserId: string,
): Promise<void> {
  const secureCookie = isHttpsRequest(request);

  // Always clear the md_ref cookie once we reach this point (one-shot).
  // Appended below unconditionally, but we only do the DB work if the cookie exists.
  const cookieRef = readMdRefCookie(request);

  const clearMdRef = `md_ref=; Max-Age=0; Path=/; HttpOnly${secureCookie ? '; Secure' : ''}; SameSite=Lax`;

  if (!cookieRef) return;
  const { linkId, clickedAt } = cookieRef;

  // Append the clear-cookie header immediately so it fires even if we throw.
  responseHeaders.append('Set-Cookie', clearMdRef);

  try {
    // 1. Parse referral config (will throw if env is misconfigured — caught below).
    const cfg = ReferralConfigSchema.parse(env);

    // 1b. Decrypt the referee's email so we can pass it to the fraud pipeline.
    //     Also used below for the canonical-index write (step 2b).
    const userWithEmail = await getUserWithDecryptedEmail(db, refereeUserId, env.PII_KEY);

    // 1c. Parse Cloudflare bot-management score from the incoming request.
    //     The `cf-bot-management` header is a JSON object; `.score` is 0-99 (lower = more likely bot).
    //     Tolerates missing / non-JSON → undefined.
    let cfBotScore: number | undefined;
    try {
      const cfBmRaw = request.headers.get('cf-bot-management');
      if (cfBmRaw) {
        const parsed = JSON.parse(cfBmRaw) as Record<string, unknown>;
        const s = parsed['score'];
        if (typeof s === 'number') cfBotScore = s;
      }
    } catch (err) {
      // malformed header → leave cfBotScore undefined
      captureCaught(err, {
        scope: 'server.referrals.welcome.parseCfBotScore',
        severity: 'info',
      });
    }

    // 2. Bind the referral row (idempotent on referee_user_id UNIQUE).
    //    visitorId and ipHash are not available in this code path (no signed
    //    context cookie / X-Forwarded-For hashing at the signup API layer);
    //    those fields are passed as undefined and adapters guard accordingly.
    const ref = await bindReferralOnSignup(db, {
      refereeUserId,
      linkId,
      clickedAt,
      refereeEmail: userWithEmail?.email ?? undefined,
      refereePhone: undefined,
      visitorId: undefined,
      ipHash: undefined,
      cfBotScore,
    });
    if (!ref) {
      // Self-referral, inactive link, fraud block, or already attributed → nothing to do.
      return;
    }

    // 2b. Write canonical email index for fraud deduplication.
    //     Decrypts via pgcrypto in-DB; only writes when email is present.
    if (userWithEmail?.email) {
      const canonical = canonicalizeEmail(userWithEmail.email);
      await updateRefereeEmailIndex(db, refereeUserId, canonical);
    }

    // 3. Mint a single-use platform-funded fixed-amount promo code.
    //    Eligibility: scope=all + firstPurchaseOnly=true + userAllowlist=[refereeUserId]
    //    This scopes the code to the referee and ensures it is only usable on their
    //    first order.  Single-use is enforced additionally by totalCap=1 / perUserCap=1.
    const code = generateWelcomeCode();
    const rulesJson: PromoRules = {
      scope: { kind: 'all' },
      eligibility: {
        firstPurchaseOnly: true,
        userAllowlist: [refereeUserId],
      },
    };

    const [promo] = await createWelcomePromo(db, {
      code,
      kind: 'fixed_amount',
      valueAmount: cfg.REFERRAL_REFEREE_DISCOUNT_AGOROT,
      valueBps: null,
      maxCapAmount: null,
      bogoBuy: null,
      bogoGetFree: null,
      funder: 'platform',
      vendorId: null,
      // authorUserId: the referee owns this promo — no system-user exists.
      authorUserId: refereeUserId,
      status: 'active',
      validFrom: null,
      validUntil: null,
      minSubtotal: null,
      maxSubtotal: null,
      totalCap: 1,
      perUserCap: 1,
      scope: rulesJson.scope as Record<string, unknown>,
      eligibility: rulesJson.eligibility as Record<string, unknown>,
      rulesJson: rulesJson as unknown as Record<string, unknown>,
      description: `Welcome discount for referred user`,
    });

    if (!promo) {
      console.error('[referral-welcome] promo insert returned no row', { refereeUserId });
      return;
    }

    // 4. Attach the promo code id to the referral row.
    await attachWelcomePromo(db, ref.id, promo.id);
  } catch (err) {
    // Referral attribution must never block signup.
    console.error('[referral-welcome] attribution failed (swallowed):', {
      name: err instanceof Error ? err.name : 'unknown',
      message: err instanceof Error ? err.message?.slice(0, 200) : String(err).slice(0, 200),
    });
    captureCaught(err, {
      scope: 'server.referrals.welcome.attributeRefereeOnSignup',
      severity: 'warning',
    });
  }
}
