import { router, protectedProcedure } from "@/src/trpc/trpc-index";
import { z } from "zod";
import {
  applyDiscountToUserOrder,
  cancelUserOrderTransaction,
  checkUserSuspended,
  db,
  deleteUserCartItemsForOrder,
  getOrderProductById,
  getUserAddressByIdAndUser,
  getUserCouponUsageForOrder,
  getUserOrderBasic,
  getUserOrderByIdWithRelations,
  getUserOrderCount,
  getUserOrdersWithRelations,
  getUserProductIdsFromOrders,
  getUserProductsForRecentOrders,
  getUserRecentlyDeliveredOrderIds,
  getUserSlotCapacityStatus,
  orders,
  orderItems,
  orderStatus,
  placeUserOrderTransaction,
  recordUserCouponUsage,
  updateUserOrderNotes,
  validateAndGetUserCoupon,
} from "@/src/dbService";
import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common";
import { scaffoldAssetUrl } from "@/src/lib/s3-client";
import { ApiError } from "@/src/lib/api-error";
import {
  sendOrderPlacedNotification,
  sendOrderCancelledNotification,
} from "@/src/lib/notif-job";
import { CONST_KEYS, getConstant, getConstants } from "@/src/lib/const-store";
import { publishFormattedOrder, publishCancellation } from "@/src/lib/post-order-handler";
import { getSlotById } from "@/src/stores/slot-store";
import type {
  UserOrdersResponse,
  UserOrderDetail,
  UserCancelOrderResponse,
  UserUpdateNotesResponse,
  UserRecentProductsResponse,
} from "@/src/dbService";

const placeOrderUtil = async (params: {
  userId: number;
  selectedItems: Array<{
    productId: number;
    quantity: number;
    slotId: number | null;
  }>;
  addressId: number;
  paymentMethod: "online" | "cod";
  couponId?: number;
  userNotes?: string;
  isFlash?: boolean;
}) => {
  const {
    userId,
    selectedItems,
    addressId,
    paymentMethod,
    couponId,
    userNotes,
  } = params;

  const constants = await getConstants<number>([
    CONST_KEYS.minRegularOrderValue,
    CONST_KEYS.deliveryCharge,
    CONST_KEYS.flashFreeDeliveryThreshold,
    CONST_KEYS.flashDeliveryCharge,
  ]);

  const isFlashDelivery = params.isFlash;
  const minOrderValue = (isFlashDelivery ? constants[CONST_KEYS.flashFreeDeliveryThreshold] : constants[CONST_KEYS.minRegularOrderValue]) || 0;
  const deliveryCharge = (isFlashDelivery ? constants[CONST_KEYS.flashDeliveryCharge] : constants[CONST_KEYS.deliveryCharge]) || 0;

  const orderGroupId = `${Date.now()}-${userId}`;

  const address = await getUserAddressByIdAndUser(addressId, userId);
  if (!address) {
    throw new ApiError("Invalid address", 400);
  }

  const ordersBySlot = new Map<
    number | null,
    Array<{
      productId: number;
      quantity: number;
      slotId: number | null;
      product: Awaited<ReturnType<typeof getOrderProductById>>;
    }>
  >();

  for (const item of selectedItems) {
    const product = await getOrderProductById(item.productId);
    if (!product) {
      throw new ApiError(`Product ${item.productId} not found`, 400);
    }

    if (!ordersBySlot.has(item.slotId)) {
      ordersBySlot.set(item.slotId, []);
    }
    ordersBySlot.get(item.slotId)!.push({ ...item, product });
  }

  if (params.isFlash) {
    for (const item of selectedItems) {
      const product = await getOrderProductById(item.productId);
      if (!product?.isFlashAvailable) {
        throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400);
      }
    }
  }

  let totalAmount = 0;
  for (const [slotId, items] of ordersBySlot) {
    const orderTotal = items.reduce(
      (sum, item) => {
        if (!item.product) return sum
        const basePrice = params.isFlash
          ? (item.product.flashPrice ?? item.product.price)
          : item.product.price
        const itemPrice = parseFloat((basePrice ?? 0).toString())
        return sum + itemPrice * item.quantity;
      },
      0
    );
    totalAmount += orderTotal;
  }

  const appliedCoupon = await validateAndGetUserCoupon(couponId, userId, totalAmount);

  const expectedDeliveryCharge =
    totalAmount < minOrderValue ? deliveryCharge : 0;

  const totalWithDelivery = totalAmount + expectedDeliveryCharge;

  type OrderData = {
    order: Omit<typeof orders.$inferInsert, "id">;
    orderItems: Omit<typeof orderItems.$inferInsert, "id">[];
    orderStatus: Omit<typeof orderStatus.$inferInsert, "id">;
  };

  const ordersData: OrderData[] = [];
  let isFirstOrder = true;

  for (const [slotId, items] of ordersBySlot) {
    const subOrderTotal = items.reduce(
      (sum, item) => {
        if (!item.product) return sum
        const basePrice = params.isFlash
          ? (item.product.flashPrice ?? item.product.price)
          : item.product.price
        const itemPrice = parseFloat((basePrice ?? 0).toString())
        return sum + itemPrice * item.quantity;
      },
      0
    );
    const subOrderTotalWithDelivery = subOrderTotal + expectedDeliveryCharge;

    const orderGroupProportion = subOrderTotal / totalAmount;
    const orderTotalAmount = isFirstOrder ? subOrderTotalWithDelivery : subOrderTotal;

    const { finalOrderTotal: finalOrderAmount } = applyDiscountToUserOrder(
      orderTotalAmount,
      appliedCoupon,
      orderGroupProportion
    );

    const order: Omit<typeof orders.$inferInsert, "id"> = {
      userId,
      addressId,
      slotId: params.isFlash ? null : slotId,
      isCod: paymentMethod === "cod",
      isOnlinePayment: paymentMethod === "online",
      paymentInfoId: null,
      totalAmount: finalOrderAmount.toString(),
      deliveryCharge: isFirstOrder ? expectedDeliveryCharge.toString() : "0",
      readableId: -1,
      userNotes: userNotes || null,
      orderGroupId,
      orderGroupProportion: orderGroupProportion.toString(),
      isFlashDelivery: params.isFlash,
    };

    const validItems = items.filter(
      (item): item is typeof item & { product: NonNullable<typeof item.product> } =>
        item.product !== null && item.product !== undefined
    )
    const orderItemsData: Omit<typeof orderItems.$inferInsert, "id">[] = validItems.map(
      (item) => {
        const basePrice = params.isFlash
          ? (item.product.flashPrice ?? item.product.price)
          : item.product.price
        const priceString = (basePrice ?? 0).toString()

        return {
          orderId: 0,
          productId: item.productId,
          quantity: item.quantity.toString(),
          price: priceString,
          discountedPrice: priceString,
        }
      }
    );

    const orderStatusData: Omit<typeof orderStatus.$inferInsert, "id"> = {
      userId,
      orderId: 0,
      paymentStatus: paymentMethod === "cod" ? "cod" : "pending",
    };

    ordersData.push({ order, orderItems: orderItemsData, orderStatus: orderStatusData });
    isFirstOrder = false;
  }

  const createdOrders = await placeUserOrderTransaction({
    userId,
    ordersData,
    paymentMethod,
    totalWithDelivery,
  });

  await deleteUserCartItemsForOrder(
    userId,
    selectedItems.map((item) => item.productId)
  );

  if (appliedCoupon && createdOrders.length > 0) {
    await recordUserCouponUsage(
      userId,
      appliedCoupon.id,
      createdOrders[0].id
    );
  }

  // for (const order of createdOrders) {
  //   sendOrderPlacedNotification(userId, order.id.toString());
  // }

  console.log('publishing the order')
  await publishFormattedOrder(createdOrders, ordersBySlot);

  return { success: true, data: createdOrders };
};

export const orderRouter = router({
  placeOrder: protectedProcedure
    .input(
      z.object({
        selectedItems: z.array(
          z.object({
            productId: z.number().int().positive(),
            quantity: z.number().int().positive(),
            slotId: z.union([z.number().int(), z.null()]),
          })
        ),
        addressId: z.number().int().positive(),
        paymentMethod: z.enum(["online", "cod"]),
        couponId: z.number().int().positive().optional(),
        userNotes: z.string().optional(),
        isFlashDelivery: z.boolean().optional().default(false),
      })
    )
    .mutation(async ({ input, ctx }) => {
      const userId = ctx.user.userId;

      const isSuspended = await checkUserSuspended(userId);
      if (isSuspended) {
        throw new ApiError("Unable to place order", 403);
      }

      const {
        selectedItems,
        addressId,
        paymentMethod,
        couponId,
        userNotes,
        isFlashDelivery,
      } = input;

      if (isFlashDelivery) {
        const isFlashDeliveryEnabled = await getConstant<boolean>(CONST_KEYS.isFlashDeliveryEnabled);
        if (!isFlashDeliveryEnabled) {
          throw new ApiError("Flash delivery is currently unavailable. Please opt for scheduled delivery.", 403);
        }
      }

      if (!isFlashDelivery) {
        const slotIds = [...new Set(selectedItems.filter(i => i.slotId !== null).map(i => i.slotId as number))];
        for (const slotId of slotIds) {
          const isCapacityFull = await getUserSlotCapacityStatus(slotId);
          if (isCapacityFull) {
            throw new ApiError("Selected delivery slot is at full capacity. Please choose another slot.", 403);
          }
        }
      }

      let processedItems = selectedItems;

      if (isFlashDelivery) {
        processedItems = selectedItems.map(item => ({
          ...item,
          slotId: null as any,
        }));
      }

      return await placeOrderUtil({
        userId,
        selectedItems: processedItems,
        addressId,
        paymentMethod,
        couponId,
        userNotes,
        isFlash: isFlashDelivery,
      });
    }),

  getOrders: protectedProcedure
    .input(
      z
        .object({
          page: z.number().min(1).default(1),
          pageSize: z.number().min(1).max(50).default(10),
        })
        .optional()
    )
    .query(async ({ input, ctx }): Promise<UserOrdersResponse> => {
      const { page = 1, pageSize = 10 } = input || {};
      const userId = ctx.user.userId;
      const offset = (page - 1) * pageSize;

      const totalCount = await getUserOrderCount(userId);
      const userOrders = await getUserOrdersWithRelations(userId, offset, pageSize);

      const mappedOrders = await Promise.all(
        userOrders.map(async (order) => {
          const status = order.orderStatus[0];
          const refund = order.refunds[0];

          type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged";
          type OrderStatus = "cancelled" | "success";

          let deliveryStatus: DeliveryStatus;
          let orderStatus: OrderStatus;

          const allItemsPackaged = order.orderItems.every(
            (item) => item.is_packaged
          );

          if (status?.isCancelled) {
            deliveryStatus = "cancelled";
            orderStatus = "cancelled";
          } else if (status?.isDelivered) {
            deliveryStatus = "success";
            orderStatus = "success";
          } else if (allItemsPackaged) {
            deliveryStatus = "packaged";
            orderStatus = "success";
          } else {
            deliveryStatus = "pending";
            orderStatus = "success";
          }

          const paymentMode = order.isCod ? "CoD" : "Online";
          const paymentStatus = status?.paymentStatus || "pending";
          const refundStatus = refund?.refundStatus || "none";
          const refundAmount = refund?.refundAmount
            ? parseFloat(refund.refundAmount.toString())
            : null;

          const items = await Promise.all(
            order.orderItems.map(async (item) => {
              const signedImages = item.product.images
                ? scaffoldAssetUrl(
                  item.product.images as string[]
                )
                : [];
              return {
                productName: item.product.name,
                quantity: parseFloat(item.quantity),
                price: parseFloat(item.price.toString()),
                discountedPrice: parseFloat(
                  item.discountedPrice?.toString() || item.price.toString()
                ),
                amount:
                  parseFloat(item.price.toString()) * parseFloat(item.quantity),
                image: signedImages[0] || null,
              };
            })
          );

          return {
            id: order.id,
            orderId: `ORD${order.id}`,
            orderDate: order.createdAt.toISOString(),
            deliveryStatus,
            deliveryDate: order.slot?.deliveryTime.toISOString(),
            orderStatus,
            cancelReason: status?.cancelReason || null,
            paymentMode,
            totalAmount: Number(order.totalAmount),
            deliveryCharge: Number(order.deliveryCharge),
            paymentStatus,
            refundStatus,
            refundAmount,
            userNotes: order.userNotes || null,
            items,
            isFlashDelivery: order.isFlashDelivery,
            createdAt: order.createdAt.toISOString(),
          };
        })
      );

      return {
        success: true,
        data: mappedOrders,
        pagination: {
          page,
          pageSize,
          totalCount,
          totalPages: Math.ceil(totalCount / pageSize),
        },
      };
    }),

  getOrderById: protectedProcedure
    .input(z.object({ orderId: z.string() }))
    .query(async ({ input, ctx }): Promise<UserOrderDetail> => {
      const { orderId } = input;
      const userId = ctx.user.userId;

      const order = await getUserOrderByIdWithRelations(parseInt(orderId), userId);

      if (!order) {
        throw new Error("Order not found");
      }

      const couponUsageData = await getUserCouponUsageForOrder(order.id);

      let couponData = null;
      if (couponUsageData.length > 0) {
        let totalDiscountAmount = 0;
        const orderTotal = parseFloat(order.totalAmount.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) => u.coupon.couponCode)
            .join(", "),
          couponDescription: `${couponUsageData.length} coupons applied`,
          discountAmount: totalDiscountAmount,
        };
      }

      const status = order.orderStatus[0];
      const refund = order.refunds[0];

      type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged";
      type OrderStatus = "cancelled" | "success";

      let deliveryStatus: DeliveryStatus;
      let orderStatusResult: OrderStatus;

      const allItemsPackaged = order.orderItems.every(
        (item) => item.is_packaged
      );

      if (status?.isCancelled) {
        deliveryStatus = "cancelled";
        orderStatusResult = "cancelled";
      } else if (status?.isDelivered) {
        deliveryStatus = "success";
        orderStatusResult = "success";
      } else if (allItemsPackaged) {
        deliveryStatus = "packaged";
        orderStatusResult = "success";
      } else {
        deliveryStatus = "pending";
        orderStatusResult = "success";
      }

      const paymentMode = order.isCod ? "CoD" : "Online";
      const paymentStatus = status?.paymentStatus || "pending";
      const refundStatus = refund?.refundStatus || "none";
      const refundAmount = refund?.refundAmount
        ? parseFloat(refund.refundAmount.toString())
        : null;

      const items = await Promise.all(
        order.orderItems.map(async (item) => {
          const signedImages = item.product.images
            ? scaffoldAssetUrl(
              item.product.images as string[]
            )
            : [];
          return {
            productName: item.product.name,
            quantity: parseFloat(item.quantity),
            price: parseFloat(item.price.toString()),
            discountedPrice: parseFloat(
              item.discountedPrice?.toString() || item.price.toString()
            ),
            amount:
              parseFloat(item.price.toString()) * parseFloat(item.quantity),
            image: signedImages[0] || null,
          };
        })
      );

      return {
        id: order.id,
        orderId: `ORD${order.id}`,
        orderDate: order.createdAt.toISOString(),
        deliveryStatus,
        deliveryDate: order.slot?.deliveryTime.toISOString(),
        orderStatus: orderStatusResult,
        cancellationStatus: orderStatusResult,
        cancelReason: status?.cancelReason || null,
        paymentMode,
        paymentStatus,
        refundStatus,
        refundAmount,
        userNotes: order.userNotes || null,
        items,
        couponCode: couponData?.couponCode || null,
        couponDescription: couponData?.couponDescription || null,
        discountAmount: couponData?.discountAmount || null,
        orderAmount: parseFloat(order.totalAmount.toString()),
        isFlashDelivery: order.isFlashDelivery,
        createdAt: order.createdAt.toISOString(),
        totalAmount: parseFloat(order.totalAmount.toString()),
        deliveryCharge: parseFloat(order.deliveryCharge.toString()),
      };
    }),

  cancelOrder: protectedProcedure
    .input(
      z.object({
        id: z.number(),
        reason: z.string().min(1, "Cancellation reason is required"),
      })
    )
    .mutation(async ({ input, ctx }): Promise<UserCancelOrderResponse> => {
      try {
        const userId = ctx.user.userId;
        const { id, reason } = input;

        const order = await getUserOrderBasic(id);

        if (!order) {
          console.error("Order not found:", id);
          throw new ApiError("Order not found", 404);
        }

        if (order.userId !== userId) {
          console.error("Order does not belong to user:", {
            orderId: id,
            orderUserId: order.userId,
            requestUserId: userId,
          });

          throw new ApiError("Order not found", 404);
        }

        const status = order.orderStatus[0];
        if (!status) {
          console.error("Order status not found for order:", id);
          throw new ApiError("Order status not found", 400);
        }

        if (status.isCancelled) {
          console.error("Order is already cancelled:", id);
          throw new ApiError("Order is already cancelled", 400);
        }

        if (status.isDelivered) {
          console.error("Cannot cancel delivered order:", id);
          throw new ApiError("Cannot cancel delivered order", 400);
        }

        await cancelUserOrderTransaction(id, status.id, reason, order.isCod);

        // await sendOrderCancelledNotification(userId, id.toString());

        await publishCancellation(id, 'user', reason);

        return { success: true, message: "Order cancelled successfully" };
      } catch (e) {
        console.log(e);
        throw new ApiError("failed to cancel order");
      }
    }),

  updateUserNotes: protectedProcedure
    .input(
      z.object({
        id: z.number(),
        userNotes: z.string(),
      })
    )
    .mutation(async ({ input, ctx }): Promise<UserUpdateNotesResponse> => {
      const userId = ctx.user.userId;
      const { id, userNotes } = input;

      const order = await getUserOrderBasic(id);

      if (!order) {
        console.error("Order not found:", id);
        throw new ApiError("Order not found", 404);
      }

      if (order.userId !== userId) {
        console.error("Order does not belong to user:", {
          orderId: id,
          orderUserId: order.userId,
          requestUserId: userId,
        });
        throw new ApiError("Order not found", 404);
      }

      const status = order.orderStatus[0];
      if (!status) {
        console.error("Order status not found for order:", id);
        throw new ApiError("Order status not found", 400);
      }

      if (status.isDelivered) {
        console.error("Cannot update notes for delivered order:", id);
        throw new ApiError("Cannot update notes for delivered order", 400);
      }

      if (status.isCancelled) {
        console.error("Cannot update notes for cancelled order:", id);
        throw new ApiError("Cannot update notes for cancelled order", 400);
      }

      await updateUserOrderNotes(id, userNotes);

      return { success: true, message: "Notes updated successfully" };
    }),

  getRecentlyOrderedProducts: protectedProcedure
    .input(
      z
        .object({
          limit: z.number().min(1).max(50).default(20),
        })
        .optional()
    )
    .query(async ({ input, ctx }): Promise<UserRecentProductsResponse> => {
      const { limit = 20 } = input || {};
      const userId = ctx.user.userId;

      const thirtyDaysAgo = new Date();
      thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

      const recentOrderIds = await getUserRecentlyDeliveredOrderIds(userId, 10, thirtyDaysAgo);

      if (recentOrderIds.length === 0) {
        return { success: true, products: [] };
      }

      const productIds = await getUserProductIdsFromOrders(recentOrderIds);

      if (productIds.length === 0) {
        return { success: true, products: [] };
      }

      const productsWithUnits = await getUserProductsForRecentOrders(productIds, limit);

      const formattedProducts = await Promise.all(
        productsWithUnits.map(async (product) => {
          const nextDeliveryDate = await getNextDeliveryDate(product.id);
          return {
            id: product.id,
            name: product.name,
            shortDescription: product.shortDescription,
            price: product.price,
            unit: product.unitShortNotation,
            incrementStep: product.incrementStep,
            isOutOfStock: product.isOutOfStock,
            nextDeliveryDate: nextDeliveryDate
              ? nextDeliveryDate.toISOString()
              : null,
            images: scaffoldAssetUrl(
              (product.images as string[]) || []
            ),
          };
        })
      );

      return {
        success: true,
        products: formattedProducts,
      };
    }),
});
