import { and, eq } from 'drizzle-orm';
import type { TxDrizzleClient } from '@/server/db/client.js';
import { affiliatePayouts } from '@/server/db/schema.js';
import type { PayoutId } from '@/server/platform-seams/ids.js';

export type AffiliatePayoutTx = Parameters<Parameters<TxDrizzleClient['transaction']>[0]>[0];

export const AFFILIATE_PAYOUT_SETTLEMENT_EVIDENCE_CONSTRAINT =
  'affiliate_payouts_settlement_evidence_uq';

export class AffiliatePayoutSettlementEvidenceConflictError extends Error {
  constructor() {
    super('AFFILIATE_PAYOUT_SETTLEMENT_EVIDENCE_CONFLICT');
    this.name = 'AffiliatePayoutSettlementEvidenceConflictError';
  }
}

function isSettlementEvidenceUniqueViolation(err: unknown): boolean {
  const dbError = err as {
    code?: string;
    constraint?: string;
    constraint_name?: string;
    cause?: { code?: string; constraint?: string; constraint_name?: string };
  } | null;
  const code = dbError?.code ?? dbError?.cause?.code;
  const constraint =
    dbError?.constraint ??
    dbError?.cause?.constraint ??
    dbError?.constraint_name ??
    dbError?.cause?.constraint_name;
  return (
    code === '23505' &&
    (constraint == null || constraint === AFFILIATE_PAYOUT_SETTLEMENT_EVIDENCE_CONSTRAINT)
  );
}

export async function approveAffiliatePayoutInTx(
  tx: AffiliatePayoutTx,
  payoutId: PayoutId,
  adminUserId: string,
  approvedAt: Date,
): Promise<void> {
  await tx
    .update(affiliatePayouts)
    .set({ status: 'approved', approvedAt, approvedByUserId: adminUserId })
    .where(eq(affiliatePayouts.id, payoutId));
}

export async function markAffiliatePayoutPaidWithEvidenceInTx(
  tx: AffiliatePayoutTx,
  payoutId: PayoutId,
  evidence: {
    settlementRail: string;
    settlementReference: string;
    settledAt: Date;
    settledByUserId: string;
  },
  paidAt: Date,
): Promise<void> {
  try {
    const transitioned = await tx
      .update(affiliatePayouts)
      .set({
        status: 'paid',
        paidAt,
        settlementRail: evidence.settlementRail,
        settlementReference: evidence.settlementReference,
        settledAt: evidence.settledAt,
        settledByUserId: evidence.settledByUserId,
      })
      .where(and(eq(affiliatePayouts.id, payoutId), eq(affiliatePayouts.status, 'approved')))
      .returning({ id: affiliatePayouts.id });
    if (transitioned.length !== 1) throw new Error('INVALID_STATUS:not_approved');
  } catch (err) {
    if (isSettlementEvidenceUniqueViolation(err)) {
      throw new AffiliatePayoutSettlementEvidenceConflictError();
    }
    throw err;
  }
}
