import { and, eq, gt, isNull } from 'drizzle-orm';
import { affiliatePayouts, sessions, users } from '@/server/db/schema.js';
import type { AffiliateAdminPrincipal } from '@/server/referrals/admin-actions.js';
import { ForbiddenError } from '@/server/api/domain-error.js';
import {
  verifyPayoutReadyForSettlement,
  type PayoutLedgerTx,
} from '@/server/referrals/payout-debit.js';
import type { PayoutId } from '@/server/platform-seams/ids.js';
import { recordAffiliateAdminAction } from '@/server/db/queries/referrals/referral-writes.js';
import {
  approveAffiliatePayoutInTx,
  markAffiliatePayoutPaidWithEvidenceInTx,
} from '@/server/db/queries/affiliate-payouts.js';

export type AffiliatePayoutAdminAction = 'approve' | 'mark_paid';

export type AffiliatePayoutSettlementEvidence = {
  settlementRail: 'bank_transfer' | 'paypal' | 'other';
  settlementReference: string;
  settledAt: Date;
  amountAgorot: number;
};

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

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

export class AffiliatePayoutAdminAuthorizationError extends ForbiddenError {
  constructor() {
    super('Admin authorization required', 'AFFILIATE_PAYOUT_ADMIN_REQUIRED');
    this.name = 'AffiliatePayoutAdminAuthorizationError';
  }
}

export class AffiliatePayoutSelfActionError extends ForbiddenError {
  constructor() {
    super('Admin cannot approve or pay their own affiliate payout', 'AFFILIATE_PAYOUT_SELF_ACTION');
    this.name = 'AffiliatePayoutSelfActionError';
  }
}

const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function isVerifiedJwtSessionSentinel(sessionId: string, userId: string): boolean {
  const prefix = `jwt:${userId}:`;
  return sessionId.startsWith(prefix) && /^\d+$/.test(sessionId.slice(prefix.length));
}

export async function authorizeAffiliatePayoutAdminInTx(
  tx: PayoutLedgerTx,
  actor: AffiliateAdminPrincipal,
): Promise<string> {
  if (!actor.user.isAdmin || actor.session.userId !== actor.user.id) {
    throw new AffiliatePayoutAdminAuthorizationError();
  }

  const jwtSession = isVerifiedJwtSessionSentinel(actor.session.id, actor.user.id);
  if (!jwtSession && !UUID_PATTERN.test(actor.session.id)) {
    throw new AffiliatePayoutAdminAuthorizationError();
  }

  const [admin] = jwtSession
    ? await tx
        .select({
          userId: users.id,
          isAdmin: users.isAdmin,
          accountState: users.accountState,
        })
        .from(users)
        .where(eq(users.id, actor.user.id))
        .for('share')
    : await tx
        .select({
          userId: sessions.userId,
          isAdmin: users.isAdmin,
          accountState: users.accountState,
        })
        .from(sessions)
        .innerJoin(users, eq(users.id, sessions.userId))
        .where(
          and(
            eq(sessions.id, actor.session.id),
            eq(sessions.userId, actor.user.id),
            isNull(sessions.revokedAt),
            gt(sessions.expiresAt, new Date()),
          ),
        )
        .for('share');
  if (!admin?.isAdmin || admin.accountState !== 'ACTIVE') {
    throw new AffiliatePayoutAdminAuthorizationError();
  }
  return admin.userId;
}

export function assertAffiliatePayoutNotSelf(payoutUserId: string, adminUserId: string): void {
  if (payoutUserId === adminUserId) throw new AffiliatePayoutSelfActionError();
}

export async function executeAffiliatePayoutAdminTransitionInTx(
  tx: PayoutLedgerTx,
  payoutId: PayoutId,
  action: AffiliatePayoutAdminAction,
  actor: AffiliateAdminPrincipal,
  evidence?: AffiliatePayoutSettlementEvidence,
) {
  if (action !== 'approve' && action !== 'mark_paid') {
    throw new Error('INVALID_AFFILIATE_PAYOUT_ADMIN_ACTION');
  }
  const adminUserId = await authorizeAffiliatePayoutAdminInTx(tx, actor);
  const [payout] = await tx
    .select({
      id: affiliatePayouts.id,
      userId: affiliatePayouts.userId,
      amountAgorot: affiliatePayouts.amountAgorot,
      status: affiliatePayouts.status,
      ledgerEntryId: affiliatePayouts.ledgerEntryId,
      enrollmentId: affiliatePayouts.enrollmentId,
      settlementRail: affiliatePayouts.settlementRail,
      settlementReference: affiliatePayouts.settlementReference,
      settledAt: affiliatePayouts.settledAt,
      settledByUserId: affiliatePayouts.settledByUserId,
      stripeTransferId: affiliatePayouts.stripeTransferId,
      stripePayoutId: affiliatePayouts.stripePayoutId,
    })
    .from(affiliatePayouts)
    .where(eq(affiliatePayouts.id, payoutId))
    .for('update')
    .limit(1);

  if (!payout) throw new Error('PAYOUT_NOT_FOUND');
  assertAffiliatePayoutNotSelf(payout.userId, adminUserId);

  if (action === 'mark_paid' && payout.status === 'paid') {
    const hasOffStripeEvidence = Boolean(payout.settlementRail && payout.settlementReference);
    const hasStripeEvidence = Boolean(payout.stripeTransferId || payout.stripePayoutId);
    if (!hasOffStripeEvidence && !hasStripeEvidence) {
      throw new AffiliatePayoutPaidWithoutEvidenceError();
    }
    return payout;
  }

  const expectedStatus = action === 'approve' ? 'requested' : 'approved';
  if (payout.status !== expectedStatus) throw new Error(`INVALID_STATUS:${payout.status}`);

  let settlementReference: string | null = null;
  if (action === 'mark_paid') {
    settlementReference = evidence?.settlementReference.trim() ?? '';
    const validRail =
      evidence?.settlementRail === 'bank_transfer' ||
      evidence?.settlementRail === 'paypal' ||
      evidence?.settlementRail === 'other';
    const validSettledAt =
      evidence?.settledAt instanceof Date &&
      !Number.isNaN(evidence.settledAt.getTime()) &&
      evidence.settledAt.getTime() <= Date.now();
    if (
      !evidence ||
      !validRail ||
      !settlementReference ||
      settlementReference.length > 255 ||
      !validSettledAt ||
      evidence.amountAgorot !== payout.amountAgorot
    ) {
      throw new AffiliatePayoutSettlementEvidenceRequiredError();
    }
  }

  await verifyPayoutReadyForSettlement(tx, payout);
  const transitionedAt = new Date();
  if (action === 'approve') {
    await approveAffiliatePayoutInTx(tx, payoutId, adminUserId, transitionedAt);
  } else {
    await markAffiliatePayoutPaidWithEvidenceInTx(
      tx,
      payoutId,
      {
        settlementRail: evidence!.settlementRail,
        settlementReference: settlementReference!,
        settledAt: evidence!.settledAt,
        settledByUserId: adminUserId,
      },
      transitionedAt,
    );
  }
  await recordAffiliateAdminAction(tx, {
    adminUserId,
    targetUserId: payout.userId,
    action: `payout_${action}`,
    payload:
      action === 'approve'
        ? {
            payoutId,
            amountAgorot: payout.amountAgorot,
            beforeStatus: payout.status,
            afterStatus: 'approved',
          }
        : {
            payoutId,
            amountAgorot: payout.amountAgorot,
            before: {
              status: payout.status,
              settlementRail: payout.settlementRail,
              settlementReference: payout.settlementReference,
              settledAt: payout.settledAt,
              settledByUserId: payout.settledByUserId,
            },
            after: {
              status: 'paid',
              settlementRail: evidence!.settlementRail,
              settlementReference: settlementReference!,
              settledAt: evidence!.settledAt.toISOString(),
              settledByUserId: adminUserId,
            },
          },
  });
  return action === 'approve'
    ? { ...payout, status: 'approved' as const }
    : {
        ...payout,
        status: 'paid' as const,
        settlementRail: evidence!.settlementRail,
        settlementReference: settlementReference!,
        settledAt: evidence!.settledAt,
        settledByUserId: adminUserId,
      };
}
