import type { TxDrizzleClient } from '@/server/db/client.js';
import { invalidateCatalog } from '@/server/cache/invalidate.js';
import * as promoQueries from '@/server/db/queries/promo-codes.js';
import * as redemptionQueries from '@/server/db/queries/promo-redemptions.js';
import {
  getLockedUserUses,
  lockPromoForUpdate,
  recordRedemption,
  releasePromoSlot,
  toPromotionsTx,
} from '@/server/promo/promotions-platform.js';
import type {
  DiscountBreakdown,
  PromoCode,
  PromoRejectReason,
  ResolvedCart,
} from '@/server/promo/types.js';
import type { PromoEffect } from './effects.js';
import type { PromoId, UserId, OrderLineId, VendorId } from '@/server/platform-seams/ids.js';

export interface ApplyEffectsContext {
  db: TxDrizzleClient;
  reserve?:
    | {
        ids: {
          promoCodeId: PromoId;
          userId: UserId;
        };
        code: PromoCode;
        user: { id: UserId; isClubMember: boolean };
        cart: ResolvedCart;
        platformFeePct?: number;
        revalidateLockedUsage: (locked: {
          globalUses: number;
          userUses: number;
        }) => Promise<
          | { ok: true; code: PromoCode; breakdown: DiscountBreakdown }
          | { ok: false; reason: PromoRejectReason }
        >;
      }
    | undefined;
  finalize?:
    | {
        ids: {
          promoCodeId: PromoId;
          purchaseId: OrderLineId;
          userId: UserId;
          vendorId: VendorId;
        };
      }
    | undefined;
}

export type ApplyEffectsResult =
  | {
      ok: true;
      insertedRedemption: boolean;
      reservedPromo?: {
        code: PromoCode;
        breakdown: DiscountBreakdown;
      };
    }
  | { ok: false; reason: PromoRejectReason };

export async function applyEffects(
  ctx: ApplyEffectsContext,
  effects: PromoEffect[],
): Promise<ApplyEffectsResult> {
  const reserveIds = ctx.reserve?.ids;
  const finalizeIds = ctx.finalize?.ids;
  let insertedRedemption = false;
  let reservedPromo:
    | {
        code: PromoCode;
        breakdown: DiscountBreakdown;
      }
    | undefined;

  for (const effect of effects) {
    switch (effect.kind) {
      case 'record-usage': {
        if (!ctx.reserve) {
          throw new Error('promo reserve context required for record-usage');
        }
        await redemptionQueries.lockUserCodeForUpdate(
          ctx.db,
          reserveIds?.promoCodeId ?? effect.promoCodeId,
          reserveIds?.userId ?? effect.userId,
        );
        const lockedPlatformPromo = await lockPromoForUpdate(
          toPromotionsTx(ctx.db),
          reserveIds?.promoCodeId ?? effect.promoCodeId,
        );
        if (!lockedPlatformPromo) return { ok: false, reason: 'NOT_FOUND' };

        const lockedUserUses = await getLockedUserUses(
          toPromotionsTx(ctx.db),
          reserveIds?.promoCodeId ?? effect.promoCodeId,
          reserveIds?.userId ?? effect.userId,
        );
        const revalidated = await ctx.reserve.revalidateLockedUsage({
          globalUses: lockedPlatformPromo.uses,
          userUses: lockedUserUses,
        });
        if (!revalidated.ok) return revalidated;

        try {
          await recordRedemption(
            toPromotionsTx(ctx.db),
            reserveIds?.promoCodeId ?? effect.promoCodeId,
            reserveIds?.userId ?? effect.userId,
          );
        } catch (error) {
          if (
            typeof error === 'object' &&
            error !== null &&
            'code' in error &&
            error.code === 'PROMO_QUOTA_EXHAUSTED'
          ) {
            const reason =
              'reason' in error && error.reason === 'per_user'
                ? 'USER_QUOTA_EXCEEDED'
                : 'OUT_OF_QUOTA';
            return { ok: false, reason };
          }
          throw error;
        }

        reservedPromo = {
          code: revalidated.code,
          breakdown: revalidated.breakdown,
        };
        for (const dealId of new Set(ctx.reserve.cart.lines.map((line) => line.dealId))) {
          await invalidateCatalog(ctx.db, { scope: 'deal', dealId });
        }
        break;
      }
      case 'release-usage':
        if (effect.userId) {
          await releasePromoSlot(
            ctx.db,
            reserveIds?.promoCodeId ?? effect.promoCodeId,
            reserveIds?.userId ?? effect.userId,
          );
        }
        break;
      case 'insert-redemption': {
        const promoCodeId = finalizeIds?.promoCodeId ?? effect.promoCodeId;
        const purchaseId = finalizeIds?.purchaseId ?? effect.purchaseId;
        const userId = finalizeIds?.userId ?? effect.userId;
        const vendorId = finalizeIds?.vendorId ?? effect.vendorId;
        const codeRow = ctx.finalize
          ? null
          : await promoQueries.getPromoCodeById(ctx.db, promoCodeId);
        if (!ctx.finalize) {
          await redemptionQueries.lockUserCodeForUpdate(ctx.db, promoCodeId, userId);
          const userCount = await redemptionQueries.countUserRedemptions(
            ctx.db,
            userId,
            promoCodeId,
          );
          if (!codeRow) return { ok: false, reason: 'NOT_FOUND' };
          if (
            codeRow.perUserCap != null &&
            codeRow.perUserCap > 0 &&
            userCount > codeRow.perUserCap
          ) {
            return { ok: false, reason: 'USER_QUOTA_EXCEEDED' };
          }
        }
        const result = await redemptionQueries.insertPromoRedemption(ctx.db, {
          promoCodeId,
          orderLineId: purchaseId,
          userId,
          vendorId,
          funder: effect.funder,
          discountAmount: effect.discountAgorot,
          appliedToLines: {
            lines: effect.appliedToLines,
            overUserCap: false,
          } as never,
        });
        insertedRedemption = result.inserted;
        break;
      }
    }
  }

  return { ok: true, insertedRedemption, reservedPromo };
}
