/**
 * EARN entry point — fraud gate fail-closed → computeCommission → accrueCommission.
 *
 * Referral earn (runReferralEarn) — fraud gate ordering composed with
 * the module commission/accrual seams. Host data routes through FraudHostStore only.
 */
import { eq } from 'drizzle-orm'
import type { PostgresTransaction } from '@platform-modules/db'
import type { LedgerSchema } from '@platform-modules/ledger'
import type { VestingSchema } from '@platform-modules/ledger/vesting'
import { accrueCommission, appendAffiliateLedgerEntry, type AccrueCommissionState } from './accrual.js'
import { computeCommission, type CommissionPolicy } from './commission.js'
import type { FraudHostStore } from './fraud/host-store.js'
import { runFraudPipeline } from './fraud/registry.js'
import type { AdapterConfig } from './fraud/types.js'
import { referralsTable, type AffiliateSchema } from './schema.js'

export interface ProcessReferralEarnInput {
  host: FraudHostStore
  policy: CommissionPolicy
  fraudConfig: Record<string, AdapterConfig>
  purchaseId: string
  buyerUserId: string
  amountPaidMinor: bigint
  platformNetMinor: bigint
  matureAt: Date
}

export type ProcessReferralEarnFailureReason =
  | 'NO_REFERRAL'
  | 'QUARANTINED'
  | 'FRAUD_HOLD'
  | 'FRAUD_BLOCK'
  | 'ZERO_COMMISSION'

export type ProcessReferralEarnResult =
  | { earned: false; reason: ProcessReferralEarnFailureReason }
  | {
      earned: true
      commissionMinor: bigint
      inserted: boolean
      state: AccrueCommissionState
    }

function platformNetToCtx(platformNetMinor: bigint): number {
  if (platformNetMinor > BigInt(Number.MAX_SAFE_INTEGER)) {
    return Number.MAX_SAFE_INTEGER
  }
  if (platformNetMinor < BigInt(Number.MIN_SAFE_INTEGER)) {
    return Number.MIN_SAFE_INTEGER
  }
  return Number(platformNetMinor)
}

/**
 * Process a settled purchase EARN: fraud EARN gate (fail-closed) first, then commission + accrual.
 */
export async function processReferralEarn<
  S extends AffiliateSchema & LedgerSchema & VestingSchema,
>(
  tx: PostgresTransaction<S>,
  input: ProcessReferralEarnInput,
): Promise<ProcessReferralEarnResult> {
  const [refRow] = await tx
    .select({
      id: referralsTable.id,
      referrerUserId: referralsTable.referrerUserId,
      quarantinedAt: referralsTable.quarantinedAt,
      visitorIdReferee: referralsTable.visitorIdReferee,
      visitorIdReferrer: referralsTable.visitorIdReferrer,
    })
    .from(referralsTable)
    .where(eq(referralsTable.refereeUserId, input.buyerUserId))
    .limit(1)

  if (!refRow) return { earned: false, reason: 'NO_REFERRAL' }
  if (refRow.quarantinedAt) return { earned: false, reason: 'QUARANTINED' }

  const [cardFingerprintReferee, referrerFingerprints] = await Promise.all([
    input.host.getUserCardFingerprint(input.buyerUserId),
    input.host.listReferrerCardFingerprints(refRow.referrerUserId),
  ])

  const earnCtx = {
    db: tx,
    host: input.host,
    referralId: refRow.id,
    referrerUserId: refRow.referrerUserId,
    refereeUserId: input.buyerUserId,
    purchaseId: input.purchaseId,
    platformNetAgorot: platformNetToCtx(input.platformNetMinor),
    visitorIdReferee: refRow.visitorIdReferee ?? undefined,
    visitorIdReferrer: refRow.visitorIdReferrer ?? undefined,
    cardFingerprintReferee: cardFingerprintReferee ?? undefined,
    cardFingerprintReferrer: referrerFingerprints[0] ?? undefined,
  }

  const { action } = await runFraudPipeline({
    point: 'EARN',
    ctx: earnCtx,
    userId: input.buyerUserId,
    referralId: refRow.id,
    fraudConfig: input.fraudConfig,
    db: tx,
    persistEvents: true,
  })

  if (action === 'block') {
    await appendAffiliateLedgerEntry(tx, {
      userId: refRow.referrerUserId,
      amountMinor: 0n,
      entryType: 'affiliate_commission',
      // 'fraud_block', never 'purchase': the audit row must not claim the
      // purchase's canonical accrual key, or a cleared false-positive could
      // never re-earn (the legit accrual would replay-no-op on the tombstone).
      sourceType: 'fraud_block',
      sourceId: input.purchaseId,
      matureAt: new Date(),
      referralId: refRow.id,
      memo: 'fraud:block',
    })
    await tx
      .update(referralsTable)
      .set({ quarantinedAt: new Date() })
      .where(eq(referralsTable.id, refRow.id))
    return { earned: false, reason: 'FRAUD_BLOCK' }
  }

  if (action === 'hold') {
    await tx
      .update(referralsTable)
      .set({ quarantinedAt: new Date() })
      .where(eq(referralsTable.id, refRow.id))
    return { earned: false, reason: 'FRAUD_HOLD' }
  }

  const commissionMinor = computeCommission(input.policy, {
    amountPaidMinor: input.amountPaidMinor,
    context: {
      referrerId: refRow.referrerUserId,
      refereeId: input.buyerUserId,
    },
  })

  if (commissionMinor <= 0n) {
    return { earned: false, reason: 'ZERO_COMMISSION' }
  }

  const accrual = await accrueCommission(tx, {
    userId: refRow.referrerUserId,
    amountMinor: commissionMinor,
    entryType: 'affiliate_commission',
    sourceType: 'purchase',
    sourceId: input.purchaseId,
    matureAt: input.matureAt,
    referralId: refRow.id,
    memo: input.purchaseId,
  })

  return {
    earned: true,
    commissionMinor,
    inserted: accrual.inserted,
    state: accrual.state,
  }
}
