import { db } from '../db/db_index'
import {
  addresses,
  complaints,
  couponUsage,
  orderItems,
  orders,
  orderStatus,
  payments,
  refunds,
} from '../db/schema'
import { and, desc, eq, gt, inArray, lt, SQL } from 'drizzle-orm'
import type {
  AdminOrderDetails,
  AdminOrderRow,
  AdminOrderStatusRecord,
  AdminOrderUpdateResult,
  AdminOrderItemPackagingResult,
  AdminOrderMessageResult,
  AdminOrderBasicResult,
  AdminGetSlotOrdersResult,
  AdminGetAllOrdersResultWithUserId,
  AdminRebalanceSlotsResult,
  AdminCancelOrderResult,
  AdminRefundRecord,
  RefundStatus,
  PaymentStatus,
} from '@packages/shared'
import type { InferSelectModel } from 'drizzle-orm'
import { coerceDate } from '../lib/date'

const isPaymentStatus = (value: string): value is PaymentStatus =>
  value === 'pending' || value === 'success' || value === 'cod' || value === 'failed'

const isRefundStatus = (value: string): value is RefundStatus =>
  value === 'success' || value === 'pending' || value === 'failed' || value === 'none' || value === 'na' || value === 'processed'

type OrderStatusRow = InferSelectModel<typeof orderStatus>

const mapOrderStatusRecord = (record: OrderStatusRow): AdminOrderStatusRecord => ({
  id: record.id,
  orderTime: record.orderTime,
  userId: record.userId,
  orderId: record.orderId,
  isPackaged: record.isPackaged,
  isDelivered: record.isDelivered,
  isCancelled: record.isCancelled,
  cancelReason: record.cancelReason ?? null,
  isCancelledByAdmin: record.isCancelledByAdmin ?? null,
  paymentStatus: isPaymentStatus(record.paymentStatus) ? record.paymentStatus : 'pending',
  cancellationUserNotes: record.cancellationUserNotes ?? null,
  cancellationAdminNotes: record.cancellationAdminNotes ?? null,
  cancellationReviewed: record.cancellationReviewed,
  cancellationReviewedAt: record.cancellationReviewedAt ?? null,
  refundCouponId: record.refundCouponId ?? null,
})

export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<AdminOrderRow | null> {
  const [result] = await db
    .update(orders)
    .set({ adminNotes })
    .where(eq(orders.id, orderId))
    .returning()
  return (result || null) as AdminOrderRow | null
}

export async function updateOrderPackaged(orderId: string, isPackaged: boolean): Promise<AdminOrderUpdateResult> {
  const orderIdNumber = parseInt(orderId)

  await db
    .update(orderItems)
    .set({ is_packaged: isPackaged })
    .where(eq(orderItems.orderId, orderIdNumber))

  if (!isPackaged) {
    await db
      .update(orderStatus)
      .set({ isPackaged, isDelivered: false })
      .where(eq(orderStatus.orderId, orderIdNumber))
  } else {
    await db
      .update(orderStatus)
      .set({ isPackaged })
      .where(eq(orderStatus.orderId, orderIdNumber))
  }

  const order = await db.query.orders.findFirst({
    where: eq(orders.id, orderIdNumber),
  })

  return { success: true, userId: order?.userId ?? null }
}

export async function updateOrderDelivered(orderId: string, isDelivered: boolean): Promise<AdminOrderUpdateResult> {
  const orderIdNumber = parseInt(orderId)

  await db
    .update(orderStatus)
    .set({ isDelivered })
    .where(eq(orderStatus.orderId, orderIdNumber))

  const order = await db.query.orders.findFirst({
    where: eq(orders.id, orderIdNumber),
  })

  return { success: true, userId: order?.userId ?? null }
}

export async function getOrderDetails(orderId: number): Promise<AdminOrderDetails | null> {
  // Single optimized query with all relations
  const orderData = await db.query.orders.findFirst({
    where: eq(orders.id, orderId),
    with: {
      user: true,
      address: true,
      slot: true,
      orderItems: {
        with: {
          product: {
            with: {
              unit: true,
            },
          },
        },
      },
      payment: true,
      paymentInfo: true,
      orderStatus: true,
      refunds: true,
    },
  })

  if (!orderData) {
    return null
  }

  const couponUsageData = await db.query.couponUsage.findMany({
    where: eq(couponUsage.orderId, orderData.id),
    with: {
      coupon: true,
    },
  })

  let couponData = null
  if (couponUsageData.length > 0) {
    let totalDiscountAmount = 0
    const orderTotal = parseFloat((orderData.totalAmount ?? '0').toString())

    for (const usage of couponUsageData) {
      let discountAmount = 0

      if (usage.coupon.discountPercent) {
        discountAmount =
          (orderTotal * parseFloat(usage.coupon.discountPercent.toString())) /
          100
      } else if (usage.coupon.flatDiscount) {
        discountAmount = parseFloat(usage.coupon.flatDiscount.toString())
      }

      if (
        usage.coupon.maxValue &&
        discountAmount > parseFloat(usage.coupon.maxValue.toString())
      ) {
        discountAmount = parseFloat(usage.coupon.maxValue.toString())
      }

      totalDiscountAmount += discountAmount
    }

    couponData = {
      couponCode: couponUsageData.map((u: any) => u.coupon.couponCode).join(', '),
      couponDescription: `${couponUsageData.length} coupons applied`,
      discountAmount: totalDiscountAmount,
    }
  }

  const statusRecord = orderData.orderStatus?.[0]
  const orderStatusRecord = statusRecord ? mapOrderStatusRecord(statusRecord) : null
  let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
  if (orderStatusRecord?.isCancelled) {
    status = 'cancelled'
  } else if (orderStatusRecord?.isDelivered) {
    status = 'delivered'
  }

  const refund = orderData.refunds?.[0]
  const refundStatus = refund?.refundStatus && isRefundStatus(refund.refundStatus)
    ? refund.refundStatus
    : null
  const refundRecord: AdminRefundRecord | null = refund
    ? {
        id: refund.id,
        orderId: refund.orderId,
        refundAmount: refund.refundAmount,
        refundStatus,
        merchantRefundId: refund.merchantRefundId,
        refundProcessedAt: refund.refundProcessedAt,
        createdAt: refund.createdAt,
      }
    : null

  return {
    id: orderData.id,
    readableId: orderData.id,
    userId: orderData.user.id,
    customerName: `${orderData.user.name}`,
    customerEmail: orderData.user.email,
    customerMobile: orderData.user.mobile,
    address: {
      name: orderData.address.name,
      line1: orderData.address.addressLine1,
      line2: orderData.address.addressLine2,
      city: orderData.address.city,
      state: orderData.address.state,
      pincode: orderData.address.pincode,
      phone: orderData.address.phone,
    },
    slotInfo: orderData.slot
      ? {
          time: (coerceDate(orderData.slot.deliveryTime) ?? new Date(0)).toISOString(),
          sequence: orderData.slot.deliverySequence,
        }
      : null,
    isCod: orderData.isCod,
    isOnlinePayment: orderData.isOnlinePayment,
    totalAmount:
      parseFloat(orderData.totalAmount?.toString() || '0') -
      parseFloat(orderData.deliveryCharge?.toString() || '0'),
    deliveryCharge: parseFloat(orderData.deliveryCharge?.toString() || '0'),
    adminNotes: orderData.adminNotes,
    userNotes: orderData.userNotes,
    createdAt: orderData.createdAt,
    status,
    isPackaged: orderStatusRecord?.isPackaged || false,
    isDelivered: orderStatusRecord?.isDelivered || false,
    items: orderData.orderItems.map((item: any) => ({
      id: item.id,
      name: item.product.name,
      quantity: item.quantity,
      productSize: item.product.productQuantity,
      price: item.price,
      unit: item.product.unit?.shortNotation,
      amount: parseFloat(item.price.toString()) * parseFloat(item.quantity || '0'),
      isPackaged: item.is_packaged,
      isPackageVerified: item.is_package_verified,
    })),
    payment: orderData.payment
      ? {
          status: orderData.payment.status,
          gateway: orderData.payment.gateway,
          merchantOrderId: orderData.payment.merchantOrderId,
        }
      : null,
    paymentInfo: orderData.paymentInfo
      ? {
          status: orderData.paymentInfo.status,
          gateway: orderData.paymentInfo.gateway,
          merchantOrderId: orderData.paymentInfo.merchantOrderId,
        }
      : null,
    cancelReason: orderStatusRecord?.cancelReason || null,
    cancellationReviewed: orderStatusRecord?.cancellationReviewed || false,
    isRefundDone: refundStatus === 'processed' || false,
    refundStatus,
    refundAmount: refund?.refundAmount
      ? parseFloat(refund.refundAmount.toString())
      : null,
    couponData,
    couponCode: couponData?.couponCode || null,
    couponDescription: couponData?.couponDescription || null,
    discountAmount: couponData?.discountAmount || null,
    orderStatus: orderStatusRecord,
    refundRecord,
    isFlashDelivery: orderData.isFlashDelivery,
  }
}

export async function updateOrderItemPackaging(
  orderItemId: number,
  isPackaged?: boolean,
  isPackageVerified?: boolean
): Promise<AdminOrderItemPackagingResult> {
  const orderItem = await db.query.orderItems.findFirst({
    where: eq(orderItems.id, orderItemId),
  })

  if (!orderItem) {
    return { success: false, updated: false }
  }

  const updateData: Partial<{
    is_packaged: boolean
    is_package_verified: boolean
  }> = {}

  if (isPackaged !== undefined) {
    updateData.is_packaged = isPackaged
  }
  if (isPackageVerified !== undefined) {
    updateData.is_package_verified = isPackageVerified
  }

  await db
    .update(orderItems)
    .set(updateData)
    .where(eq(orderItems.id, orderItemId))

  return { success: true, updated: true }
}

export async function removeDeliveryCharge(orderId: number): Promise<AdminOrderMessageResult | null> {
  const order = await db.query.orders.findFirst({
    where: eq(orders.id, orderId),
  })

  if (!order) {
    return null
  }

  const currentDeliveryCharge = parseFloat(order.deliveryCharge?.toString() || '0')
  const currentTotalAmount = parseFloat(order.totalAmount?.toString() || '0')
  const newTotalAmount = currentTotalAmount - currentDeliveryCharge

  await db
    .update(orders)
    .set({
      deliveryCharge: '0',
      totalAmount: newTotalAmount.toString(),
    })
    .where(eq(orders.id, orderId))

  return { success: true, message: 'Delivery charge removed' }
}

export async function getSlotOrders(slotId: string): Promise<AdminGetSlotOrdersResult> {
  const slotOrders = await db.query.orders.findMany({
    where: eq(orders.slotId, parseInt(slotId)),
    with: {
      user: true,
      address: true,
      slot: true,
      orderItems: {
        with: {
          product: {
            with: {
              unit: true,
            },
          },
        },
      },
      orderStatus: true,
    },
  })

  const filteredOrders = slotOrders.filter((order: any) => {
    const statusRecord = order.orderStatus[0]
    return order.isCod || (statusRecord && statusRecord.paymentStatus === 'success')
  })

  const formattedOrders = filteredOrders.map((order: any) => {
    const statusRecord = order.orderStatus[0]
    let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
    if (statusRecord?.isCancelled) {
      status = 'cancelled'
    } else if (statusRecord?.isDelivered) {
      status = 'delivered'
    }

    const items = order.orderItems.map((item: any) => ({
      id: item.id,
      name: item.product.name,
      quantity: parseFloat(item.quantity),
      price: parseFloat(item.price.toString()),
      amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
      unit: item.product.unit?.shortNotation || '',
      isPackaged: item.is_packaged,
      isPackageVerified: item.is_package_verified,
    }))

    const paymentMode: 'COD' | 'Online' = order.isCod ? 'COD' : 'Online'

    return {
      id: order.id,
      readableId: order.id,
      customerName: order.user.name || order.user.mobile+'',
      address: `${order.address.addressLine1}${
        order.address.addressLine2 ? `, ${order.address.addressLine2}` : ''
      }, ${order.address.city}, ${order.address.state} - ${
        order.address.pincode
      }, Phone: ${order.address.phone}`,
      addressId: order.addressId,
      latitude: order.address.adminLatitude ?? order.address.latitude,
      longitude: order.address.adminLongitude ?? order.address.longitude,
      totalAmount: parseFloat(order.totalAmount),
      items,
      deliveryTime: coerceDate(order.slot?.deliveryTime)?.toISOString() || null,
      status,
      isPackaged: order.orderItems.every((item: any) => item.is_packaged) || false,
      isDelivered: statusRecord?.isDelivered || false,
      isCod: order.isCod,
      paymentMode,
      paymentStatus: isPaymentStatus(statusRecord?.paymentStatus || 'pending')
        ? statusRecord?.paymentStatus || 'pending'
        : 'pending',
      slotId: order.slotId,
      adminNotes: order.adminNotes,
      userNotes: order.userNotes,
    }
  })

  return { success: true, data: formattedOrders }
}

export async function updateAddressCoords(
  addressId: number,
  latitude: number,
  longitude: number
): Promise<AdminOrderBasicResult> {
  const result = await db
    .update(addresses)
    .set({
      adminLatitude: latitude,
      adminLongitude: longitude,
    })
    .where(eq(addresses.id, addressId))
    .returning()

  return { success: result.length > 0 }
}

type GetAllOrdersInput = {
  cursor?: number
  limit: number
  slotId?: number | null
  packagedFilter?: 'all' | 'packaged' | 'not_packaged'
  deliveredFilter?: 'all' | 'delivered' | 'not_delivered'
  cancellationFilter?: 'all' | 'cancelled' | 'not_cancelled'
  flashDeliveryFilter?: 'all' | 'flash' | 'regular'
}

export async function getAllOrders(input: GetAllOrdersInput): Promise<AdminGetAllOrdersResultWithUserId> {
  const {
    cursor,
    limit,
    slotId,
    packagedFilter,
    deliveredFilter,
    cancellationFilter,
    flashDeliveryFilter,
  } = input

  let whereCondition: SQL<unknown> | undefined = undefined

  if(!cursor) {
    // Get max order ID for initial pagination
    const [maxOrder] = await db
      .select()
      .from(orders)
      .orderBy(desc(orders.id))
      .limit(1)

    const maxId = maxOrder?.id ?? 0
    const threshold = maxId - limit - 5
    whereCondition = and(whereCondition, gt(orders.id, threshold))
  }
  if (cursor) {
    whereCondition = and(whereCondition, lt(orders.id, cursor))
    whereCondition = and(whereCondition, gt(orders.id, cursor-limit-5))
  }
  if (slotId) {
    whereCondition = and(whereCondition, eq(orders.slotId, slotId))
  }
  if (packagedFilter === 'packaged') {
    whereCondition = and(whereCondition, eq(orderStatus.isPackaged, true))
  } else if (packagedFilter === 'not_packaged') {
    whereCondition = and(whereCondition, eq(orderStatus.isPackaged, false))
  }
  if (deliveredFilter === 'delivered') {
    whereCondition = and(whereCondition, eq(orderStatus.isDelivered, true))
  } else if (deliveredFilter === 'not_delivered') {
    whereCondition = and(whereCondition, eq(orderStatus.isDelivered, false))
  }
  if (cancellationFilter === 'cancelled') {
    whereCondition = and(whereCondition, eq(orderStatus.isCancelled, true))
  } else if (cancellationFilter === 'not_cancelled') {
    whereCondition = and(whereCondition, eq(orderStatus.isCancelled, false))
  }
  if (flashDeliveryFilter === 'flash') {
    whereCondition = and(whereCondition, eq(orders.isFlashDelivery, true))
  } else if (flashDeliveryFilter === 'regular') {
    whereCondition = and(whereCondition, eq(orders.isFlashDelivery, false))
  }

  const allOrders = await db.query.orders.findMany({
    where: whereCondition,
    orderBy: desc(orders.createdAt),
    limit: limit + 1,
    with: {
      user: true,
      address: true,
      slot: true,
      orderItems: {
        with: {
          product: {
            with: {
              unit: true,
            },
          },
        },
      },
      orderStatus: true,
    },
  })

  const hasMore = allOrders.length > limit
  const ordersToReturn = hasMore ? allOrders.slice(0, limit) : allOrders

  const filteredOrders = ordersToReturn.filter((order: any) => {
    const statusRecord = order.orderStatus[0]
    return order.isCod || (statusRecord && statusRecord.paymentStatus === 'success')
  })

  const formattedOrders = filteredOrders.map((order: any) => {
    const statusRecord = order.orderStatus[0]
    let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
    if (statusRecord?.isCancelled) {
      status = 'cancelled'
    } else if (statusRecord?.isDelivered) {
      status = 'delivered'
    }

    const items = order.orderItems
      .map((item: any) => ({
        id: item.id,
        name: item.product.name,
        quantity: parseFloat(item.quantity),
        price: parseFloat(item.price.toString()),
        amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
        unit: item.product.unit?.shortNotation || '',
        productSize: item.product.productQuantity,
        isPackaged: item.is_packaged,
        isPackageVerified: item.is_package_verified,
      }))
      .sort((first: any, second: any) => first.id - second.id)

    return {
      id: order.id,
      orderId: order.id.toString(),
      readableId: order.id,
      customerName: order.user.name || order.user.mobile + '',
      customerMobile: order.user.mobile,
      address: `${order.address.addressLine1}${
        order.address.addressLine2 ? `, ${order.address.addressLine2}` : ''
      }, ${order.address.city}, ${order.address.state} - ${
        order.address.pincode
      }, Phone: ${order.address.phone}`,
      addressId: order.addressId,
      latitude: order.address.adminLatitude ?? order.address.latitude,
      longitude: order.address.adminLongitude ?? order.address.longitude,
      totalAmount: parseFloat(order.totalAmount),
      deliveryCharge: parseFloat(order.deliveryCharge || '0'),
      items,
      createdAt: order.createdAt,
      deliveryTime: coerceDate(order.slot?.deliveryTime)?.toISOString() || null,
      status,
      isPackaged: order.orderItems.every((item: any) => item.is_packaged) || false,
      isDelivered: statusRecord?.isDelivered || false,
      isCod: order.isCod,
      isFlashDelivery: order.isFlashDelivery,
      userNotes: order.userNotes,
      adminNotes: order.adminNotes,
      userNegativityScore: 0,
      userId: order.userId,
    }
  })

  return {
    orders: formattedOrders,
    nextCursor: hasMore ? ordersToReturn[ordersToReturn.length - 1].id : undefined,
  }
}

export async function rebalanceSlots(slotIds: number[]): Promise<AdminRebalanceSlotsResult> {
  const ordersList = await db.query.orders.findMany({
    where: inArray(orders.slotId, slotIds),
    with: {
      orderItems: {
        with: {
          product: true,
        },
      },
      couponUsages: {
        with: {
          coupon: true,
        },
      },
    },
  })

  const processedOrdersData = ordersList.map((order: any) => {
    let newTotal = order.orderItems.reduce((acc: number, item: any) => {
      const latestPrice = +item.product.price
      const amount = latestPrice * Number(item.quantity)
      return acc + amount
    }, 0)

    order.orderItems.forEach((item: any) => {
      item.price = item.product.price
      item.discountedPrice = item.product.price
    })

    const coupon = order.couponUsages[0]?.coupon

    let discount = 0
    if (coupon && !coupon.isInvalidated && (!coupon.validTill || new Date(coupon.validTill) > new Date())) {
      const proportion = Number(order.orderGroupProportion || 1)
      if (coupon.discountPercent) {
        const maxDiscount = Number(coupon.maxValue || Infinity) * proportion
        discount = Math.min((newTotal * parseFloat(coupon.discountPercent)) / 100, maxDiscount)
      } else {
        discount = Number(coupon.flatDiscount) * proportion
      }
    }
    newTotal -= discount

    const { couponUsages, orderItems: orderItemsRaw, ...rest } = order
    const updatedOrderItems = orderItemsRaw.map((item: any) => {
      const { product, ...rawOrderItem } = item
      return rawOrderItem
    })
    return { order: rest, updatedOrderItems, newTotal }
  })

  const updatedOrderIds: number[] = []
  await db.transaction(async (tx) => {
    for (const { order, updatedOrderItems, newTotal } of processedOrdersData) {
      await tx.update(orders).set({ totalAmount: newTotal.toString() }).where(eq(orders.id, order.id))
      updatedOrderIds.push(order.id)

      for (const item of updatedOrderItems) {
        await tx
          .update(orderItems)
          .set({
            price: item.price,
            discountedPrice: item.discountedPrice,
          })
          .where(eq(orderItems.id, item.id))
      }
    }
  })

  return {
    success: true,
    updatedOrders: updatedOrderIds,
    message: `Rebalanced ${updatedOrderIds.length} orders.`,
  }
}

export async function cancelOrder(orderId: number, reason: string): Promise<AdminCancelOrderResult> {
  const order = await db.query.orders.findFirst({
    where: eq(orders.id, orderId),
    with: {
      orderStatus: true,
    },
  })

  if (!order) {
    return { success: false, message: 'Order not found', error: 'order_not_found' }
  }

  const status = order.orderStatus[0]
  if (!status) {
    return { success: false, message: 'Order status not found', error: 'status_not_found' }
  }

  if (status.isCancelled) {
    return { success: false, message: 'Order is already cancelled', error: 'already_cancelled' }
  }

  if (status.isDelivered) {
    return { success: false, message: 'Cannot cancel delivered order', error: 'already_delivered' }
  }

  const result = await db.transaction(async (tx) => {
    await tx
      .update(orderStatus)
      .set({
        isCancelled: true,
        isCancelledByAdmin: true,
        cancelReason: reason,
        cancellationAdminNotes: reason,
        cancellationReviewed: true,
        cancellationReviewedAt: new Date(),
      })
      .where(eq(orderStatus.id, status.id))

    const refundStatus = order.isCod ? 'na' : 'pending'

    await tx.insert(refunds).values({
      orderId: order.id,
      refundStatus,
    })

    return { orderId: order.id, userId: order.userId }
  })

  return {
    success: true,
    message: 'Order cancelled successfully',
    orderId: result.orderId,
    userId: result.userId,
  }
}

export async function deleteOrderById(orderId: number): Promise<void> {
  await db.transaction(async (tx) => {
    await tx.delete(orderItems).where(eq(orderItems.orderId, orderId))
    await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId))
    await tx.delete(payments).where(eq(payments.orderId, orderId))
    await tx.delete(refunds).where(eq(refunds.orderId, orderId))
    await tx.delete(couponUsage).where(eq(couponUsage.orderId, orderId))
    await tx.delete(complaints).where(eq(complaints.orderId, orderId))
    await tx.delete(orders).where(eq(orders.id, orderId))
  })
}
