import { db } from '../db/db_index'
import {
  couponApplicableProducts,
  couponApplicableUsers,
  couponUsage,
  coupons,
  reservedCoupons,
} from '../db/schema'
import { and, eq, gt, isNull, or } from 'drizzle-orm'
import type { InferSelectModel } from 'drizzle-orm'
import type { UserCoupon, UserCouponApplicableProduct, UserCouponApplicableUser, UserCouponUsage, UserCouponWithRelations } from '@packages/shared'

type CouponRow = InferSelectModel<typeof coupons>
type CouponUsageRow = InferSelectModel<typeof couponUsage>
type CouponApplicableUserRow = InferSelectModel<typeof couponApplicableUsers>
type CouponApplicableProductRow = InferSelectModel<typeof couponApplicableProducts>
type ReservedCouponRow = InferSelectModel<typeof reservedCoupons>

const mapCoupon = (coupon: CouponRow): UserCoupon => ({
  id: coupon.id,
  couponCode: coupon.couponCode,
  isUserBased: coupon.isUserBased,
  discountPercent: coupon.discountPercent ? coupon.discountPercent.toString() : null,
  flatDiscount: coupon.flatDiscount ? coupon.flatDiscount.toString() : null,
  minOrder: coupon.minOrder ? coupon.minOrder.toString() : null,
  productIds: coupon.productIds,
  maxValue: coupon.maxValue ? coupon.maxValue.toString() : null,
  isApplyForAll: coupon.isApplyForAll,
  validTill: coupon.validTill ?? null,
  maxLimitForUser: coupon.maxLimitForUser ?? null,
  isInvalidated: coupon.isInvalidated,
  exclusiveApply: coupon.exclusiveApply,
  createdAt: coupon.createdAt,
})

const mapUsage = (usage: CouponUsageRow): UserCouponUsage => ({
  id: usage.id,
  userId: usage.userId,
  couponId: usage.couponId,
  orderId: usage.orderId ?? null,
  orderItemId: usage.orderItemId ?? null,
  usedAt: usage.usedAt,
})

const mapApplicableUser = (applicable: CouponApplicableUserRow): UserCouponApplicableUser => ({
  id: applicable.id,
  couponId: applicable.couponId,
  userId: applicable.userId,
})

const mapApplicableProduct = (applicable: CouponApplicableProductRow): UserCouponApplicableProduct => ({
  id: applicable.id,
  couponId: applicable.couponId,
  productId: applicable.productId,
})

const mapCouponWithRelations = (coupon: CouponRow & {
  usages: CouponUsageRow[]
  applicableUsers: CouponApplicableUserRow[]
  applicableProducts: CouponApplicableProductRow[]
}): UserCouponWithRelations => ({
  ...mapCoupon(coupon),
  usages: coupon.usages.map(mapUsage),
  applicableUsers: coupon.applicableUsers.map(mapApplicableUser),
  applicableProducts: coupon.applicableProducts.map(mapApplicableProduct),
})

export async function getActiveCouponsWithRelations(userId: number): Promise<UserCouponWithRelations[]> {
  const allCoupons = await db.query.coupons.findMany({
    where: and(
      eq(coupons.isInvalidated, false),
      or(
        isNull(coupons.validTill),
        gt(coupons.validTill, new Date())
      )
    ),
    with: {
      usages: {
        where: eq(couponUsage.userId, userId),
      },
      applicableUsers: true,
      applicableProducts: true,
    },
  })

  return allCoupons.map(mapCouponWithRelations)
}

export async function getAllCouponsWithRelations(userId: number): Promise<UserCouponWithRelations[]> {
  const allCoupons = await db.query.coupons.findMany({
    with: {
      usages: {
        where: eq(couponUsage.userId, userId),
      },
      applicableUsers: true,
      applicableProducts: true,
    },
  })

  return allCoupons.map(mapCouponWithRelations)
}

export async function getReservedCouponByCode(secretCode: string): Promise<ReservedCouponRow | null> {
  const reserved = await db.query.reservedCoupons.findFirst({
    where: and(
      eq(reservedCoupons.secretCode, secretCode.toUpperCase()),
      eq(reservedCoupons.isRedeemed, false)
    ),
  })

  return reserved || null
}

export async function redeemReservedCoupon(userId: number, reservedCoupon: ReservedCouponRow): Promise<UserCoupon> {
  const couponResult = await db.transaction(async (tx) => {
    const [coupon] = await tx.insert(coupons).values({
      couponCode: reservedCoupon.couponCode,
      isUserBased: true,
      discountPercent: reservedCoupon.discountPercent,
      flatDiscount: reservedCoupon.flatDiscount,
      minOrder: reservedCoupon.minOrder,
      productIds: reservedCoupon.productIds,
      maxValue: reservedCoupon.maxValue,
      isApplyForAll: false,
      validTill: reservedCoupon.validTill,
      maxLimitForUser: reservedCoupon.maxLimitForUser,
      exclusiveApply: reservedCoupon.exclusiveApply,
      createdBy: reservedCoupon.createdBy,
    }).returning()

    await tx.insert(couponApplicableUsers).values({
      couponId: coupon.id,
      userId,
    })

    await tx.update(reservedCoupons).set({
      isRedeemed: true,
      redeemedBy: userId,
      redeemedAt: new Date(),
    }).where(eq(reservedCoupons.id, reservedCoupon.id))

    return coupon
  })

  return mapCoupon(couponResult)
}
