import { LoyaltyAccount, LoyaltyTxn } from "@/db/models";
import type { LoyaltyTxnType } from "@/db/types";
import { AppError } from "@/lib/errors";
import { getCommerceSettings } from "@/services/commerce";
import { isLoyaltyMysql } from "@/db/activeDatabase";
import {
  adjustLoyaltyMysql,
  ensureLoyaltyMysql,
} from "@/db/mysql/loyalty";

export async function ensureLoyalty(userId: string) {
  if (isLoyaltyMysql()) {
    return ensureLoyaltyMysql(userId);
  }
  let account = await LoyaltyAccount.findOne({ userId });
  if (!account) {
    account = await LoyaltyAccount.create({ userId, points: 0 });
  }
  return account;
}

export async function adjustLoyalty(params: {
  userId: string;
  delta: number;
  type: LoyaltyTxnType;
  bookingId?: string;
  note?: string;
}) {
  if (!params.delta) throw new AppError(400, "Invalid points delta");

  if (isLoyaltyMysql()) {
    try {
      return await adjustLoyaltyMysql(params);
    } catch (e: any) {
      if (e?.status === 400 || String(e?.message || "").includes("Insufficient")) {
        throw new AppError(400, "Insufficient loyalty points");
      }
      throw e;
    }
  }

  const account = await ensureLoyalty(params.userId);
  const next = (account as any).points + params.delta;
  if (next < 0) throw new AppError(400, "Insufficient loyalty points");
  (account as any).points = next;
  await (account as any).save();

  const txn = await LoyaltyTxn.create({
    userId: params.userId,
    type: params.type,
    delta: params.delta,
    pointsAfter: (account as any).points,
    bookingId: params.bookingId,
    note: params.note,
  });

  return { account, txn };
}

export async function earnPointsForPayment(userId: string, paidLyd: number, bookingId: string) {
  const settings = await getCommerceSettings();
  if (!settings.loyaltyEnabled || paidLyd <= 0) return { points: 0 };
  const points = Math.floor(paidLyd * settings.pointsPerLyd);
  if (points <= 0) return { points: 0 };
  await adjustLoyalty({
    userId,
    delta: points,
    type: "EARN",
    bookingId,
    note: "Points earned from booking payment",
  });
  return { points };
}

/** Convert redeem points → TND discount capped by settings + subtotal. */
export async function pointsToDiscountTnd(params: {
  userId: string;
  pointsToRedeem: number;
  subtotalTnd: number;
}) {
  const settings = await getCommerceSettings();
  if (!settings.loyaltyEnabled || params.pointsToRedeem <= 0) {
    return { pointsRedeemed: 0, discountTnd: 0 };
  }
  if (params.pointsToRedeem < settings.minRedeemPoints) {
    throw new AppError(400, `Minimum redeem is ${settings.minRedeemPoints} points`);
  }
  const account = await ensureLoyalty(params.userId);
  const points = Number((account as { points: number }).points);
  if (points < params.pointsToRedeem) {
    throw new AppError(400, "Insufficient loyalty points");
  }
  let discountTnd = Math.round(params.pointsToRedeem * settings.tndPerPoint * 100) / 100;
  const maxDiscount =
    Math.round(params.subtotalTnd * (settings.maxRedeemPercentOfSubtotal / 100) * 100) / 100;
  if (discountTnd > maxDiscount) discountTnd = maxDiscount;
  const pointsRedeemed = Math.min(
    params.pointsToRedeem,
    Math.ceil(discountTnd / settings.tndPerPoint),
  );
  const finalDiscount = Math.round(pointsRedeemed * settings.tndPerPoint * 100) / 100;
  return { pointsRedeemed, discountTnd: finalDiscount };
}
