import { Coupon, CouponRedemption } from "@/db/models";
import { AppError } from "@/lib/errors";
import { getCommerceSettings } from "@/services/commerce";
import { isMysqlActive } from "@/db/activeDatabase";
import { createId } from "@/db/ids";
import type { RowDataPacket } from "mysql2/promise";
import { connectMysql, mysqlPool, sqlExecute, sqlQuery, withMysqlTxn } from "@/db/mysql/pool";

function roundMoney(value: number) {
  return Math.round(value * 100) / 100;
}

async function ensureMysql() {
  try {
    mysqlPool();
  } catch {
    await connectMysql();
  }
}

type CouponLike = {
  _id: string;
  id: string;
  code: string;
  type: string;
  value: number;
  maxUses: number | null;
  usedCount: number;
  minNights: number | null;
  minSubtotalTnd: number | null;
  validFrom: Date | null;
  validTo: Date | null;
  active: boolean;
};

function mapCouponRow(r: RowDataPacket): CouponLike {
  return {
    _id: String(r.id),
    id: String(r.id),
    code: String(r.code),
    type: String(r.type),
    value: Number(r.value),
    maxUses: r.max_uses == null ? null : Number(r.max_uses),
    usedCount: Number(r.used_count || 0),
    minNights: r.min_nights == null ? null : Number(r.min_nights),
    minSubtotalTnd: r.min_subtotal_tnd == null ? null : Number(r.min_subtotal_tnd),
    validFrom: r.valid_from ? new Date(r.valid_from) : null,
    validTo: r.valid_to ? new Date(r.valid_to) : null,
    active: Boolean(r.active),
  };
}

function computeDiscount(coupon: CouponLike, subtotalTnd: number, nights: number) {
  if (!coupon.active) throw new AppError(400, "Invalid coupon code");

  const now = Date.now();
  if (coupon.validFrom && coupon.validFrom.getTime() > now) {
    throw new AppError(400, "Coupon not yet valid");
  }
  if (coupon.validTo && coupon.validTo.getTime() < now) {
    throw new AppError(400, "Coupon expired");
  }
  if (coupon.maxUses != null && coupon.usedCount >= coupon.maxUses) {
    throw new AppError(400, "Coupon fully redeemed");
  }
  if (coupon.minNights != null && nights < coupon.minNights) {
    throw new AppError(400, `Coupon requires at least ${coupon.minNights} nights`);
  }
  if (coupon.minSubtotalTnd != null && subtotalTnd < coupon.minSubtotalTnd) {
    throw new AppError(400, "Order below coupon minimum");
  }

  let discountTnd = 0;
  if (coupon.type === "PERCENT") {
    discountTnd = roundMoney(subtotalTnd * (coupon.value / 100));
  } else {
    discountTnd = roundMoney(coupon.value);
  }
  return Math.min(discountTnd, subtotalTnd);
}

export async function resolveCouponDiscount(params: {
  code?: string;
  nights: number;
  subtotalTnd: number;
  userId: string;
}) {
  const settings = await getCommerceSettings();
  if (!settings.couponsEnabled || !params.code?.trim()) {
    return { discountTnd: 0, coupon: null as null | any, code: undefined as string | undefined };
  }

  const code = params.code.trim().toUpperCase();

  if (isMysqlActive()) {
    await ensureMysql();
    const rows = await sqlQuery<RowDataPacket[]>(
      `SELECT * FROM coupons WHERE code = ? LIMIT 1`,
      [code],
    );
    const coupon = rows[0] ? mapCouponRow(rows[0]) : null;
    if (!coupon) throw new AppError(400, "Invalid coupon code");
    const discountTnd = computeDiscount(coupon, params.subtotalTnd, params.nights);
    return { discountTnd, coupon, code };
  }

  const coupon = await Coupon.findOne({ code }).lean();
  if (!coupon || !coupon.active) throw new AppError(400, "Invalid coupon code");

  const shaped: CouponLike = {
    _id: String(coupon._id),
    id: String(coupon._id),
    code: String(coupon.code),
    type: String(coupon.type),
    value: Number(coupon.value),
    maxUses: coupon.maxUses == null ? null : Number(coupon.maxUses),
    usedCount: Number(coupon.usedCount || 0),
    minNights: coupon.minNights == null ? null : Number(coupon.minNights),
    minSubtotalTnd: coupon.minSubtotalTnd == null ? null : Number(coupon.minSubtotalTnd),
    validFrom: coupon.validFrom ? new Date(coupon.validFrom) : null,
    validTo: coupon.validTo ? new Date(coupon.validTo) : null,
    active: Boolean(coupon.active),
  };
  const discountTnd = computeDiscount(shaped, params.subtotalTnd, params.nights);
  return { discountTnd, coupon: shaped, code };
}

export async function recordCouponRedemption(params: {
  couponId: string;
  code: string;
  userId: string;
  bookingId: string;
  discountTnd: number;
}) {
  if (isMysqlActive()) {
    await ensureMysql();
    return withMysqlTxn(async (conn) => {
      const [existing] = await conn.query<RowDataPacket[]>(
        `SELECT id FROM coupon_redemptions WHERE booking_id = ? LIMIT 1 FOR UPDATE`,
        [params.bookingId],
      );
      if (existing[0]) return;
      const now = new Date();
      const [updated] = await conn.execute<any>(
        `UPDATE coupons SET used_count = used_count + 1, updated_at = ?
         WHERE id = ? AND active = 1 AND (max_uses IS NULL OR used_count < max_uses)`,
        [now, params.couponId],
      );
      if (updated.affectedRows !== 1) throw new AppError(409, "Coupon fully redeemed");
      await conn.execute(
      `INSERT INTO coupon_redemptions
         (id, coupon_id, user_id, booking_id, discount_tnd, code, created_at)
       VALUES (?,?,?,?,?,?,?)`,
      [
        createId(),
        params.couponId,
        params.userId,
        params.bookingId,
        params.discountTnd,
        params.code,
        now,
      ],
      );
    });
  }

  await CouponRedemption.create({
    couponId: params.couponId,
    code: params.code,
    userId: params.userId,
    bookingId: params.bookingId,
    discountTnd: params.discountTnd,
  });
  await Coupon.updateOne({ _id: params.couponId }, { $inc: { usedCount: 1 } });
}
