/**
 * Referral attribution helpers.
 *
 * resolveRefCode   — look up an active referral_links row by code.
 * isSelfReferral   — detect same-account or same-phone-blind-index collisions.
 * bindReferralOnSignup — last-click bind of a new referee to the link owner.
 */

import { eq, and, inArray } from 'drizzle-orm';
import type { DrizzleClient } from '../db/client.js';
import { affiliateEnrollments, referralLinks, referrals, users } from '../db/schema.js';
import '@/server/referrals/fraud/register-all.js';
import { runFraudPipeline } from '@/server/referrals/fraud/registry.js';
import { getReferralSettings } from './settings.js';
import type { SignupCtx } from './fraud/types.js';
import { createPendingReferral } from '@/server/db/queries/referrals/referral-writes.js';

// ---------------------------------------------------------------------------
// resolveRefCode
// ---------------------------------------------------------------------------

/**
 * Return the active referral_links row for the given code, or null if not
 * found / inactive.
 */
export async function resolveRefCode(
  db: DrizzleClient,
  code: string,
): Promise<typeof referralLinks.$inferSelect | null> {
  const [link] = await db
    .select()
    .from(referralLinks)
    .where(and(eq(referralLinks.code, code), eq(referralLinks.active, true)));
  return link ?? null;
}

// ---------------------------------------------------------------------------
// isSelfReferral
// ---------------------------------------------------------------------------

/**
 * Returns true when the referrer and referee are the same account, OR when
 * they share the same phone blind-index (i.e. the same real phone number
 * registered under two different accounts).
 */
export async function isSelfReferral(
  db: DrizzleClient,
  args: { referrerUserId: string; refereeUserId: string },
): Promise<boolean> {
  if (args.referrerUserId === args.refereeUserId) return true;

  const rows = await db
    .select({ id: users.id, phoneIndex: users.phoneIndex })
    .from(users)
    .where(inArray(users.id, [args.referrerUserId, args.refereeUserId]));

  const referrer = rows.find((x) => x.id === args.referrerUserId);
  const referee = rows.find((x) => x.id === args.refereeUserId);

  return !!(
    referrer?.phoneIndex &&
    referee?.phoneIndex &&
    referrer.phoneIndex === referee.phoneIndex
  );
}

// ---------------------------------------------------------------------------
// bindReferralOnSignup
// ---------------------------------------------------------------------------

/**
 * Bind a newly-registered referee to the link owner as a pending referral.
 *
 * Semantics:
 *  - Last-click (cookie overwritten on each touch; most recent click before
 *    signup binds) / one-per-account: referrals.refereeUserId is UNIQUE, so a
 *    second call for the same referee silently returns the pre-existing row.
 *  - Self-referral guard: returns null when the link owner is the referee.
 *  - Inactive link guard: returns null when the link no longer exists or is
 *    inactive.
 *
 * Returns the referrals row (new or pre-existing), or null on guard failure.
 */
export interface BindReferralInput {
  refereeUserId: string;
  linkId: string;
  clickedAt?: Date | null;
  refereeEmail?: string;
  refereePhone?: string;
  visitorId?: string;
  ipHash?: string;
  cfBotScore?: number;
}

export async function bindReferralOnSignup(
  db: DrizzleClient,
  args: BindReferralInput,
): Promise<typeof referrals.$inferSelect | null> {
  const [link] = await db.select().from(referralLinks).where(eq(referralLinks.id, args.linkId));

  if (!link || !link.active) return null;
  if (link.kind === 'affiliate') {
    const [enrollment] = await db
      .select({ status: affiliateEnrollments.status })
      .from(affiliateEnrollments)
      .where(eq(affiliateEnrollments.userId, link.ownerUserId))
      .limit(1);
    if (enrollment?.status !== 'active') return null;
  }
  if (link.ownerUserId === args.refereeUserId) return null; // self-referral
  if (
    await isSelfReferral(db, {
      referrerUserId: link.ownerUserId,
      refereeUserId: args.refereeUserId,
    })
  ) {
    return null;
  }

  // Run fraud pipeline AFTER link/self-referral guards, BEFORE insert.
  const settings = await getReferralSettings(db);
  const signupCtx: SignupCtx = {
    db,
    refereeUserId: args.refereeUserId,
    refereeEmail: args.refereeEmail ?? '',
    refereePhone: args.refereePhone,
    visitorId: args.visitorId,
    ipHash: args.ipHash,
    cfBotScore: args.cfBotScore,
  };
  const { action } = await runFraudPipeline({
    point: 'SIGNUP',
    ctx: signupCtx,
    userId: args.refereeUserId,
    fraudConfig: settings.fraudConfig,
    db,
    persistEvents: true,
  });

  if (action === 'block') {
    return null; // do not insert referral row
  }

  const inserted = await createPendingReferral(db, {
    referrerUserId: link.ownerUserId,
    refereeUserId: args.refereeUserId,
    linkId: link.id,
    kind: link.kind,
    status: 'pending',
    clickedAt: args.clickedAt ?? new Date(),
    quarantinedAt: action === 'hold' ? new Date() : null,
  });

  if (inserted.length > 0) return inserted[0]!;

  // Referee already bound — return the pre-existing row.
  const [existing] = await db
    .select()
    .from(referrals)
    .where(eq(referrals.refereeUserId, args.refereeUserId));
  return existing ?? null;
}
