import {
  applyPromo,
  promo as promoTable,
  promoUserUsage,
  pushSchema,
  recordRedemption,
  validatePromo,
  type DiscountLine,
  type DiscountableCart,
  type DiscountResult,
  type Promo,
  type PromoContext,
  type PromoEligibility,
  type PromoFunder,
  type PromoKind as PlatformPromoKind,
  type PromoRejectReason as PlatformRejectReason,
  type PromoScope,
  type PromoValidation,
  type PromotionsSchema,
} from '@platform-modules/commerce-promotions';
import { and, eq, sql } from 'drizzle-orm';
import type { Querier, Transaction } from '@platform-modules/db';
import type { DrizzleClient, DrizzleDb, TxDrizzleClient } from '@/server/db/client';
import { promoAllowlist } from '@/server/db/schema.js';
import type { DiscountBreakdown, PromoRejectReason, ResolvedCart } from './types.js';
import type { PromoKind } from '@/lib/enums/promo-kind';
import {
  asDealId,
  asOrderLineId,
  asPromoId,
  asUserId,
  asVendorId,
  toModuleRef,
} from '@/server/platform-seams/ids.js';

export function toPromotionsDb(db: DrizzleDb): Querier<PromotionsSchema> {
  return db as unknown as Querier<PromotionsSchema>;
}

export function toPromotionsTx(db: TxDrizzleClient): Transaction<PromotionsSchema> {
  return db as unknown as Transaction<PromotionsSchema>;
}

type HostRulesJson = {
  scope?: {
    kind: 'all' | 'deals' | 'categories' | 'tags' | 'vendor';
    dealIds?: string[];
    categoryIds?: string[];
    tagIds?: string[];
    vendorId?: string;
  };
  eligibility?: {
    firstPurchaseOnly?: boolean;
    clubOnly?: boolean;
    userAllowlist?: string[];
  };
};

type HostPromoRow = {
  id: string;
  code: string;
  kind: string;
  valueBps: number | null;
  valueAmount: number | null;
  funder: string;
  vendorId: string | null;
  status: string;
  validFrom: Date | null;
  validUntil: Date | null;
  minSubtotal: number | null;
  maxSubtotal: number | null;
  totalCap: number | null;
  redemptionCount: number;
  perUserCap: number | null;
  maxCapAmount: number | null;
  bogoBuy: number | null;
  bogoGetFree: number | null;
  rulesJson: unknown;
};

function mapHostScopeToPlatformScope(scope: NonNullable<HostRulesJson['scope']>): PromoScope {
  switch (scope.kind) {
    case 'deals':
      return {
        kind: 'products',
        ids: (scope.dealIds ?? []).map((id) => toModuleRef(asDealId(id))),
      };
    case 'categories':
      return { kind: 'categories', ids: scope.categoryIds ?? [] };
    case 'tags':
      return { kind: 'tags', tags: scope.tagIds ?? [] };
    case 'vendor':
      return scope.vendorId
        ? { kind: 'vendor', vendorId: toModuleRef(asVendorId(scope.vendorId)) }
        : { kind: 'all' };
    case 'all':
      return { kind: 'all' };
  }
}

function mapHostEligibilityToPlatformEligibility(
  eligibility: NonNullable<HostRulesJson['eligibility']>,
): PromoEligibility {
  return {
    ...(eligibility.firstPurchaseOnly ? { firstPurchaseOnly: true } : {}),
    ...(eligibility.clubOnly ? { membersOnly: true } : {}),
    ...(eligibility.userAllowlist && eligibility.userAllowlist.length > 0
      ? { allowlistOnly: true }
      : {}),
  };
}

export function mapHostPromoToModule(row: HostPromoRow): Promo & { uses: number } {
  const kind: PlatformPromoKind =
    row.kind === 'fixed_amount' ? 'fixed' : (row.kind as PlatformPromoKind);

  const rules = (row.rulesJson ?? {}) as HostRulesJson;
  const scope = mapHostScopeToPlatformScope(rules.scope ?? { kind: 'all' });
  const eligibility = mapHostEligibilityToPlatformEligibility(rules.eligibility ?? {});

  return {
    id: toModuleRef(asPromoId(row.id)),
    code: row.code,
    kind,
    ...(row.valueBps !== null && kind === 'percentage' ? { valueBps: row.valueBps } : {}),
    ...(row.valueAmount !== null && kind !== 'percentage'
      ? { valueAmount: BigInt(row.valueAmount) }
      : {}),
    currency: 'ILS',
    ...(row.maxCapAmount !== null && kind !== 'fixed'
      ? { maxDiscountAmount: BigInt(row.maxCapAmount) }
      : {}),
    ...(kind === 'bogo' && row.bogoBuy !== null && row.bogoGetFree !== null
      ? { bogo: { buyQty: row.bogoBuy, getQty: row.bogoGetFree } }
      : {}),
    scope,
    eligibility,
    funder: row.funder as PromoFunder,
    ...(row.totalCap !== null ? { maxUses: row.totalCap } : {}),
    ...(row.perUserCap ? { perUserCap: row.perUserCap } : {}),
    ...(row.validFrom ? { startsAt: row.validFrom } : {}),
    ...(row.validUntil ? { endsAt: row.validUntil } : {}),
    ...(row.minSubtotal !== null ? { minOrderAmount: BigInt(row.minSubtotal) } : {}),
    ...(row.maxSubtotal !== null ? { maxOrderAmount: BigInt(row.maxSubtotal) } : {}),
    active: row.status === 'active',
    vendorId: row.vendorId === null ? null : toModuleRef(asVendorId(row.vendorId)),
    uses: row.redemptionCount,
  };
}

/** Fetch live usage counts + host allowlist state for an already-resolved promo id. */
export async function getPromoUsageCounts(
  db: DrizzleClient | DrizzleDb,
  promoId: string,
  userId: string,
): Promise<{
  globalUses: number;
  userUses: number;
  userAllowlist: string[];
}> {
  const [gRow, uRow, allowlistRow] = await Promise.all([
    (db as DrizzleDb)
      .select({ uses: promoTable.uses })
      .from(promoTable)
      .where(eq(promoTable.id, toModuleRef(asPromoId(promoId))))
      .limit(1),
    (db as DrizzleDb)
      .select({ uses: promoUserUsage.uses })
      .from(promoUserUsage)
      .where(
        and(
          eq(promoUserUsage.promoId, toModuleRef(asPromoId(promoId))),
          eq(promoUserUsage.userId, toModuleRef(asUserId(userId))),
        ),
      )
      .limit(1),
    (db as DrizzleDb)
      .select({ userId: promoAllowlist.userId })
      .from(promoAllowlist)
      .where(and(eq(promoAllowlist.promoId, promoId), eq(promoAllowlist.userId, userId)))
      .limit(1),
  ]);

  return {
    globalUses: gRow[0]?.uses ?? 0,
    userUses: uRow[0]?.uses ?? 0,
    userAllowlist: allowlistRow.length > 0 ? [userId] : [],
  };
}

/** Acquire row-level lock on platform promo row; returns current uses count. */
export async function lockPromoForUpdate(
  db: Transaction<PromotionsSchema>,
  promoId: string,
): Promise<{ uses: number } | null> {
  const rows = await db
    .select({ uses: promoTable.uses })
    .from(promoTable)
    .where(eq(promoTable.id, toModuleRef(asPromoId(promoId))))
    .for('update')
    .limit(1);
  return rows[0] ? { uses: rows[0].uses } : null;
}

/**
 * Fetch current per-user usage count from platform table (post-lock read).
 * Call inside the same TX as lockPromoForUpdate.
 */
export async function getLockedUserUses(
  db: Transaction<PromotionsSchema>,
  promoId: string,
  userId: string,
): Promise<number> {
  const rows = await db
    .select({ uses: promoUserUsage.uses })
    .from(promoUserUsage)
    .where(
      and(
        eq(promoUserUsage.promoId, toModuleRef(asPromoId(promoId))),
        eq(promoUserUsage.userId, toModuleRef(asUserId(userId))),
      ),
    )
    .limit(1);
  return rows[0]?.uses ?? 0;
}

/**
 * Release a checkout-time promo reservation.
 * Decrements both global (promo.uses) and per-user (promo_user_usage.uses) counts.
 * Floors at 0 (idempotent for double-release).
 */
export async function releasePromoSlot(
  db: DrizzleClient | DrizzleDb,
  promoId: string,
  userId: string,
): Promise<void> {
  await Promise.all([
    (db as DrizzleDb).execute(
      sql`UPDATE promo SET uses = GREATEST(0, uses - 1), updated_at = NOW() WHERE id = ${toModuleRef(asPromoId(promoId))}::uuid`,
    ),
    (db as DrizzleDb).execute(
      sql`UPDATE promo_user_usage SET uses = GREATEST(0, uses - 1), updated_at = NOW() WHERE promo_id = ${toModuleRef(asPromoId(promoId))}::uuid AND user_id = ${toModuleRef(asUserId(userId))}`,
    ),
  ]);
}

/** Build a platform PromoContext from host cart + user state. */
export function buildPromoContext(
  cart: ResolvedCart,
  user: { id: string; isClubMember: boolean },
  userPriorPurchaseCount: number,
  globalUses: number,
  userUses: number,
  userAllowlist: string[],
): PromoContext {
  return {
    now: new Date(),
    userId: toModuleRef(asUserId(user.id)),
    cartSubtotal: BigInt(cart.totalAgorot),
    cartCurrency: 'ILS',
    lines: cart.lines.map((l) => ({
      lineId: toModuleRef(asOrderLineId(l.lineId)),
      unitPrice: BigInt(l.unitPriceAgorot),
      qty: l.quantity,
      vendorId: toModuleRef(asVendorId(l.vendorId)),
      productId: toModuleRef(asDealId(l.dealId)),
      categoryIds: l.categoryIds,
      tags: l.tagIds,
    })),
    isFirstPurchase: userPriorPurchaseCount === 0,
    isMember: user.isClubMember,
    isAllowlisted: userAllowlist.includes(user.id),
    globalUses,
    userRedemptionCount: userUses,
  };
}

/** Convert host ResolvedCart to platform DiscountableCart. */
export function resolvedCartToDiscountableCart(cart: ResolvedCart): DiscountableCart {
  return {
    currency: 'ILS',
    lines: cart.lines.map((l) => ({
      lineId: toModuleRef(asOrderLineId(l.lineId)),
      unitPrice: BigInt(l.unitPriceAgorot),
      qty: l.quantity,
      vendorId: toModuleRef(asVendorId(l.vendorId)),
      productId: toModuleRef(asDealId(l.dealId)),
      categoryIds: l.categoryIds,
      tags: l.tagIds,
    })),
  };
}

/**
 * Convert platform DiscountResult to host DiscountBreakdown.
 * Note: platform uses round-half-up percentage rounding; host uses Math.floor.
 * Difference is ≤ 1 agora per line — acceptable parity delta in Phase 3.
 */
export function discountResultToBreakdown(
  result: DiscountResult,
  promo: Promo,
  linesByLineId: Map<string, { vendorId: string }>,
): DiscountBreakdown {
  const kind: PromoKind = promo.kind === 'fixed' ? 'fixed_amount' : (promo.kind as PromoKind);
  return {
    totalDiscountAgorot: Number(result.total),
    funder: promo.funder,
    kind,
    perLine: result.perLine.map((l) => ({
      lineId: toModuleRef(asOrderLineId(l.lineId)),
      vendorId: linesByLineId.get(l.lineId)?.vendorId ?? '',
      amountAgorot: Number(l.amount),
    })),
  };
}

const PLATFORM_REASON_MAP = {
  inactive: 'INACTIVE',
  not_started: 'NOT_YET_VALID',
  expired: 'EXPIRED',
  currency_mismatch: 'INACTIVE',
  min_order_not_met: 'SUBTOTAL_TOO_LOW',
  max_order_exceeded: 'SUBTOTAL_TOO_HIGH',
  out_of_scope: 'SCOPE_MISMATCH',
  not_first_purchase: 'NEW_USERS_ONLY',
  not_member: 'CLUB_ONLY',
  not_on_allowlist: 'NOT_IN_ALLOWLIST',
  quota_exhausted: 'OUT_OF_QUOTA',
  per_user_cap_reached: 'USER_QUOTA_EXCEEDED',
} satisfies Record<PlatformRejectReason, PromoRejectReason>;

export function mapPlatformReason(r: PlatformRejectReason): PromoRejectReason {
  return PLATFORM_REASON_MAP[r];
}

export { applyPromo, pushSchema, recordRedemption, validatePromo };
export type {
  DiscountLine,
  DiscountableCart,
  DiscountResult,
  Promo,
  PromoContext,
  PromoValidation,
  PromotionsSchema,
};
