import { router, protectedProcedure } from '@/src/trpc/trpc-index'
import { z } from 'zod';
import { db } from '@/src/db/db_index'
import { coupons, users, staffUsers, orders, couponApplicableUsers, couponApplicableProducts, orderStatus, reservedCoupons } from '@/src/db/schema'
import { eq, and, like, or, inArray, lt } from 'drizzle-orm';
import dayjs from 'dayjs';

const createCouponBodySchema = z.object({
  couponCode: z.string().optional(),
  isUserBased: z.boolean().optional(),
  discountPercent: z.number().optional(),
  flatDiscount: z.number().optional(),
  minOrder: z.number().optional(),
  targetUser: z.number().optional(),
  productIds: z.array(z.number()).optional().nullable(),
  applicableUsers: z.array(z.number()).optional(),
  applicableProducts: z.array(z.number()).optional(),
  maxValue: z.number().optional(),
  isApplyForAll: z.boolean().optional(),
  validTill: z.string().optional(),
  maxLimitForUser: z.number().optional(),
  exclusiveApply: z.boolean().optional(),
});

const validateCouponBodySchema = z.object({
  code: z.string(),
  userId: z.number(),
  orderAmount: z.number(),
});

export const couponRouter = router({
  create: protectedProcedure
    .input(createCouponBodySchema)
    .mutation(async ({ input, ctx }) => {
      const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;

      // Validation: ensure at least one discount type is provided
      if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
        throw new Error("Either discountPercent or flatDiscount must be provided (but not both)");
      }

      // If user-based, applicableUsers is required (unless it's apply for all)
      if (isUserBased && (!applicableUsers || applicableUsers.length === 0) && !isApplyForAll) {
        throw new Error("applicableUsers is required for user-based coupons (or set isApplyForAll to true)");
      }

      // Cannot be both user-based and apply for all
      if (isUserBased && isApplyForAll) {
        throw new Error("Cannot be both user-based and apply for all users");
      }

      // If applicableUsers is provided, verify users exist
      if (applicableUsers && applicableUsers.length > 0) {
        const existingUsers = await db.query.users.findMany({
          where: inArray(users.id, applicableUsers),
          columns: { id: true },
        });
        if (existingUsers.length !== applicableUsers.length) {
          throw new Error("Some applicable users not found");
        }
      }

      // Get staff user ID from auth middleware
      const staffUserId = ctx.staffUser?.id;
      if (!staffUserId) {
        throw new Error("Unauthorized");
      }

      // Generate coupon code if not provided
      let finalCouponCode = couponCode;
      if (!finalCouponCode) {
        // Generate a unique coupon code
        const timestamp = Date.now().toString().slice(-6);
        const random = Math.random().toString(36).substring(2, 8).toUpperCase();
        finalCouponCode = `MF${timestamp}${random}`;
      }

      // Check if coupon code already exists
      const existingCoupon = await db.query.coupons.findFirst({
        where: eq(coupons.couponCode, finalCouponCode),
      });

      if (existingCoupon) {
        throw new Error("Coupon code already exists");
      }

       const result = await db.insert(coupons).values({
          couponCode: finalCouponCode,
          isUserBased: isUserBased || false,
          discountPercent: discountPercent?.toString(),
          flatDiscount: flatDiscount?.toString(),
          minOrder: minOrder?.toString(),
          productIds: productIds || null,
          createdBy: staffUserId,
          maxValue: maxValue?.toString(),
          isApplyForAll: isApplyForAll || false,
          validTill: validTill ? dayjs(validTill).toDate() : undefined,
          maxLimitForUser: maxLimitForUser,
          exclusiveApply: exclusiveApply || false,
        }).returning();

      const coupon = result[0];

      // Insert applicable users
      if (applicableUsers && applicableUsers.length > 0) {
        await db.insert(couponApplicableUsers).values(
          applicableUsers.map(userId => ({
            couponId: coupon.id,
            userId,
          }))
        );
      }

      // Insert applicable products
      if (applicableProducts && applicableProducts.length > 0) {
        await db.insert(couponApplicableProducts).values(
          applicableProducts.map(productId => ({
            couponId: coupon.id,
            productId,
          }))
        );
      }

      return coupon;
    }),

  getAll: protectedProcedure
    .input(z.object({
      cursor: z.number().optional(),
      limit: z.number().default(50),
      search: z.string().optional(),
    }))
    .query(async ({ input }) => {
      const { cursor, limit, search } = input;
      
      let whereCondition = undefined;
      const conditions = [];
      
      if (cursor) {
        conditions.push(lt(coupons.id, cursor));
      }
      
      if (search && search.trim()) {
        conditions.push(like(coupons.couponCode, `%${search}%`));
      }
      
      if (conditions.length > 0) {
        whereCondition = and(...conditions);
      }
      
      const result = await db.query.coupons.findMany({
        where: whereCondition,
        with: {
          creator: true,
          applicableUsers: {
            with: {
              user: true,
            },
          },
          applicableProducts: {
            with: {
              product: true,
            },
          },
        },
        orderBy: (coupons, { desc }) => [desc(coupons.createdAt)],
        limit: limit + 1,
      });
      
      const hasMore = result.length > limit;
      const couponsList = hasMore ? result.slice(0, limit) : result;
      const nextCursor = hasMore ? result[result.length - 1].id : undefined;
      
      return { coupons: couponsList, nextCursor };
    }),

  getById: protectedProcedure
    .input(z.object({ id: z.number() }))
    .query(async ({ input }) => {
      const couponId = input.id;

      const result = await db.query.coupons.findFirst({
        where: eq(coupons.id, couponId),
         with: {
           creator: true,
          applicableUsers: {
            with: {
              user: true,
            },
          },
          applicableProducts: {
            with: {
              product: true,
            },
          },
        },
      });

      if (!result) {
        throw new Error("Coupon not found");
      }

      return {
        ...result,
        productIds: (result.productIds as number[]) || undefined,
        applicableUsers: result.applicableUsers.map(au => au.user),
        applicableProducts: result.applicableProducts.map(ap => ap.product),
      };
    }),

  update: protectedProcedure
    .input(z.object({
      id: z.number(),
      updates: createCouponBodySchema.extend({
        isInvalidated: z.boolean().optional(),
      }),
    }))
    .mutation(async ({ input }) => {
      const { id, updates } = input;

      // Validation: ensure discount types are valid
      if (updates.discountPercent !== undefined && updates.flatDiscount !== undefined) {
        if (updates.discountPercent && updates.flatDiscount) {
          throw new Error("Cannot have both discountPercent and flatDiscount");
        }
      }

       // If updating to user-based, applicableUsers is required
        if (updates.isUserBased && (!updates.applicableUsers || updates.applicableUsers.length === 0)) {
          const existingCount = await db.$count(couponApplicableUsers, eq(couponApplicableUsers.couponId, id));
          if (existingCount === 0) {
            throw new Error("applicableUsers is required for user-based coupons");
          }
        }

       // If applicableUsers is provided, verify users exist
       if (updates.applicableUsers && updates.applicableUsers.length > 0) {
         const existingUsers = await db.query.users.findMany({
           where: inArray(users.id, updates.applicableUsers),
           columns: { id: true },
         });
         if (existingUsers.length !== updates.applicableUsers.length) {
           throw new Error("Some applicable users not found");
         }
       }

      const updateData: any = { ...updates };
      delete updateData.applicableUsers; // Remove since we use couponApplicableUsers table
      if (updates.discountPercent !== undefined) {
        updateData.discountPercent = updates.discountPercent?.toString();
      }
      if (updates.flatDiscount !== undefined) {
        updateData.flatDiscount = updates.flatDiscount?.toString();
      }
      if (updates.minOrder !== undefined) {
        updateData.minOrder = updates.minOrder?.toString();
      }
       if (updates.maxValue !== undefined) {
         updateData.maxValue = updates.maxValue?.toString();
       }
       if (updates.validTill !== undefined) {
         updateData.validTill = updates.validTill ? dayjs(updates.validTill).toDate() : null;
       }
      
      const result = await db.update(coupons)
        .set(updateData)
        .where(eq(coupons.id, id))
        .returning();

      if (result.length === 0) {
        throw new Error("Coupon not found");
      }

      console.log('updated coupon successfully')

      // Update applicable users: delete existing and insert new
      if (updates.applicableUsers !== undefined) {
        await db.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
        if (updates.applicableUsers.length > 0) {
          await db.insert(couponApplicableUsers).values(
            updates.applicableUsers.map(userId => ({
              couponId: id,
              userId,
            }))
          );
        }
      }

      // Update applicable products: delete existing and insert new
      if (updates.applicableProducts !== undefined) {
        await db.delete(couponApplicableProducts).where(eq(couponApplicableProducts.couponId, id));
        if (updates.applicableProducts.length > 0) {
          await db.insert(couponApplicableProducts).values(
            updates.applicableProducts.map(productId => ({
              couponId: id,
              productId,
            }))
          );
        }
      }

      return result[0];
    }),

  delete: protectedProcedure
    .input(z.object({ id: z.number() }))
    .mutation(async ({ input }) => {
      const { id } = input;

      const result = await db.update(coupons)
        .set({ isInvalidated: true })
        .where(eq(coupons.id, id))
        .returning();

      if (result.length === 0) {
        throw new Error("Coupon not found");
      }

      return { message: "Coupon invalidated successfully" };
    }),

  validate: protectedProcedure
    .input(validateCouponBodySchema)
    .query(async ({ input }) => {
      const { code, userId, orderAmount } = input;

      if (!code || typeof code !== 'string') {
        return { valid: false, message: "Invalid coupon code" };
      }

      const coupon = await db.query.coupons.findFirst({
        where: and(
          eq(coupons.couponCode, code.toUpperCase()),
          eq(coupons.isInvalidated, false)
        ),
      });

      if (!coupon) {
        return { valid: false, message: "Coupon not found or invalidated" };
      }

      // Check expiry date
      if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
        return { valid: false, message: "Coupon has expired" };
      }

      // Check if coupon applies to all users or specific user
      if (!coupon.isApplyForAll && !coupon.isUserBased) {
        return { valid: false, message: "Coupon is not available for use" };
      }

      // Check minimum order amount
      const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
      if (minOrderValue > 0 && orderAmount < minOrderValue) {
        return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
      }

      // Calculate discount
      let discountAmount = 0;
      if (coupon.discountPercent) {
        const percent = parseFloat(coupon.discountPercent);
        discountAmount = (orderAmount * percent) / 100;
      } else if (coupon.flatDiscount) {
        discountAmount = parseFloat(coupon.flatDiscount);
      }

      // Apply max value limit
      const maxValueLimit = coupon.maxValue ? parseFloat(coupon.maxValue) : 0;
      if (maxValueLimit > 0 && discountAmount > maxValueLimit) {
        discountAmount = maxValueLimit;
      }

       return {
         valid: true,
         discountAmount,
         coupon: {
           id: coupon.id,
           discountPercent: coupon.discountPercent,
           flatDiscount: coupon.flatDiscount,
           maxValue: coupon.maxValue,
         }
       };
     }),

       generateCancellationCoupon: protectedProcedure
        .input(
          z.object({
            orderId: z.number(),
          })
        )
        .mutation(async ({ input, ctx }) => {
          const { orderId } = input;

          // Get staff user ID from auth middleware
          const staffUserId = ctx.staffUser?.id;
          if (!staffUserId) {
            throw new Error("Unauthorized");
          }

          // Find the order with user and order status information
          const order = await db.query.orders.findFirst({
            where: eq(orders.id, orderId),
            with: {
              user: true,
              orderStatus: true,
            },
          });

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

         // Check if order is cancelled (check if any status entry has isCancelled: true)
        //  const isOrderCancelled = order.orderStatus?.some(status => status.isCancelled) || false;
        //  if (!isOrderCancelled) {
        //    throw new Error("Order is not cancelled");
        //  }

        //  // Check if payment method is COD
        //  if (order.isCod) {
        //    throw new Error("Can't generate refund coupon for CoD Order");
        //  }

         // Verify user exists
         if (!order.user) {
           throw new Error("User not found for this order");
         }

          // Generate coupon code: first 3 letters of user name or mobile + orderId
          const userNamePrefix = (order.user.name || order.user.mobile || 'USR').substring(0, 3).toUpperCase();
         const couponCode = `${userNamePrefix}${orderId}`;

         // Check if coupon code already exists
         const existingCoupon = await db.query.coupons.findFirst({
           where: eq(coupons.couponCode, couponCode),
         });

         if (existingCoupon) {
           throw new Error("Coupon code already exists");
         }

         // Get order total amount
         const orderAmount = parseFloat(order.totalAmount);

         // Calculate expiry date (30 days from now)
         const expiryDate = new Date();
         expiryDate.setDate(expiryDate.getDate() + 30);

          // Create the coupon and update order status in a transaction
          const coupon = await db.transaction(async (tx) => {
             // Create the coupon
              const result = await tx.insert(coupons).values({
                couponCode,
                isUserBased: true,
                flatDiscount: orderAmount.toString(),
                minOrder: orderAmount.toString(),
                maxValue: orderAmount.toString(),
                validTill: expiryDate,
                maxLimitForUser: 1,
               createdBy: staffUserId,
               isApplyForAll: false,
             }).returning();

            const coupon = result[0];

            // Insert applicable users
            await tx.insert(couponApplicableUsers).values({
              couponId: coupon.id,
              userId: order.userId,
            });

            // Update order_status with refund coupon ID
            await tx.update(orderStatus)
              .set({ refundCouponId: coupon.id })
              .where(eq(orderStatus.orderId, orderId));

            return coupon;
          });

          return coupon;
        }),

  getReservedCoupons: protectedProcedure
    .input(z.object({
      cursor: z.number().optional(),
      limit: z.number().default(50),
      search: z.string().optional(),
    }))
    .query(async ({ input }) => {
      const { cursor, limit, search } = input;

      let whereCondition = undefined;
      const conditions = [];
      
      if (cursor) {
        conditions.push(lt(reservedCoupons.id, cursor));
      }
      
      if (search && search.trim()) {
        conditions.push(or(
          like(reservedCoupons.secretCode, `%${search}%`),
          like(reservedCoupons.couponCode, `%${search}%`)
        ));
      }
      
      if (conditions.length > 0) {
        whereCondition = and(...conditions);
      }

      const result = await db.query.reservedCoupons.findMany({
        where: whereCondition,
        with: {
          redeemedUser: true,
          creator: true,
        },
        orderBy: (reservedCoupons, { desc }) => [desc(reservedCoupons.createdAt)],
        limit: limit + 1, // Fetch one extra to check if there's more
      });

      const hasMore = result.length > limit;
      const coupons = hasMore ? result.slice(0, limit) : result;
      const nextCursor = hasMore ? result[result.length - 1].id : undefined;

      return {
        coupons,
        nextCursor,
      };
    }),

  createReservedCoupon: protectedProcedure
    .input(createCouponBodySchema)
    .mutation(async ({ input, ctx }) => {
      const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;

      // Validation: ensure at least one discount type is provided
      if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
        throw new Error("Either discountPercent or flatDiscount must be provided (but not both)");
      }

      // For reserved coupons, applicableUsers is not used, as it's redeemed by one user

      // Get staff user ID from auth middleware
      const staffUserId = ctx.staffUser?.id;
      if (!staffUserId) {
        throw new Error("Unauthorized");
      }

      // Generate secret code if not provided (use couponCode as base)
      let secretCode = couponCode || `SECRET${Date.now().toString().slice(-6)}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;

      // Check if secret code already exists
      const existing = await db.query.reservedCoupons.findFirst({
        where: eq(reservedCoupons.secretCode, secretCode),
      });

      if (existing) {
        throw new Error("Secret code already exists");
      }

      const result = await db.insert(reservedCoupons).values({
        secretCode,
        couponCode: couponCode || `RESERVED${Date.now().toString().slice(-6)}`,
        discountPercent: discountPercent?.toString(),
        flatDiscount: flatDiscount?.toString(),
        minOrder: minOrder?.toString(),
        productIds,
        maxValue: maxValue?.toString(),
        validTill: validTill ? dayjs(validTill).toDate() : undefined,
        maxLimitForUser,
        exclusiveApply: exclusiveApply || false,
        createdBy: staffUserId,
      }).returning();

      const coupon = result[0];

      // Insert applicable products if provided
      if (applicableProducts && applicableProducts.length > 0) {
        await db.insert(couponApplicableProducts).values(
          applicableProducts.map(productId => ({
            couponId: coupon.id,
            productId,
          }))
        );
      }

      return coupon;
    }),

  getUsersMiniInfo: protectedProcedure
    .input(z.object({
      search: z.string().optional(),
      limit: z.number().min(1).max(50).default(20),
      offset: z.number().min(0).default(0),
    }))
    .query(async ({ input }) => {
      const { search, limit } = input;

      let whereCondition = undefined;
      if (search && search.trim()) {
        whereCondition = or(
          like(users.name, `%${search}%`),
          like(users.mobile, `%${search}%`)
        );
      }

      const userList = await db.query.users.findMany({
        where: whereCondition,
        columns: {
          id: true,
          name: true,
          mobile: true,
        },
        limit: limit,
        offset: input.offset,
        orderBy: (users, { asc }) => [asc(users.name)],
      });

      return {
        users: userList.map(user => ({
          id: user.id,
          name: user.name || 'Unknown',
          mobile: user.mobile,
        }))
      };
    }),

    createCoupon: protectedProcedure
      .input(z.object({
        mobile: z.string().min(1, 'Mobile number is required'),
      }))
      .mutation(async ({ input, ctx }) => {
        const { mobile } = input;

        // Get staff user ID from auth middleware
        const staffUserId = ctx.staffUser?.id;
        if (!staffUserId) {
          throw new Error("Unauthorized");
        }

        // Clean mobile number (remove non-digits)
        const cleanMobile = mobile.replace(/\D/g, '');

        // Validate: exactly 10 digits
        if (cleanMobile.length !== 10) {
          throw new Error("Mobile number must be exactly 10 digits");
        }

        // Check if user exists, create if not
        let user = await db.query.users.findFirst({
          where: eq(users.mobile, cleanMobile),
        });

        if (!user) {
          // Create new user
          const [newUser] = await db.insert(users).values({
            name: null,
            email: null,
            mobile: cleanMobile,
          }).returning();
          user = newUser;
        }

        // Generate unique coupon code
        const timestamp = Date.now().toString().slice(-6);
        const random = Math.random().toString(36).substring(2, 6).toUpperCase();
        const couponCode = `MF${cleanMobile.slice(-4)}${timestamp}${random}`;

        // Check if coupon code already exists (very unlikely but safe)
        const existingCode = await db.query.coupons.findFirst({
          where: eq(coupons.couponCode, couponCode),
        });

        if (existingCode) {
          throw new Error("Generated coupon code already exists - please try again");
        }

        // Create the coupon
        const [coupon] = await db.insert(coupons).values({
          couponCode,
          isUserBased: true,
          discountPercent: "20", // 20% discount
          minOrder: "1000", // ₹1000 minimum order
          maxValue: "500", // ₹500 maximum discount
          maxLimitForUser: 1, // One-time use
          isApplyForAll: false,
          exclusiveApply: false,
          createdBy: staffUserId,
          validTill: dayjs().add(90, 'days').toDate(), // 90 days from now
        }).returning();

        // Associate coupon with user
        await db.insert(couponApplicableUsers).values({
          couponId: coupon.id,
          userId: user.id,
        });

        return {
          success: true,
          coupon: {
            id: coupon.id,
            couponCode: coupon.couponCode,
            userId: user.id,
            userMobile: user.mobile,
            discountPercent: 20,
            minOrder: 1000,
            maxValue: 500,
            maxLimitForUser: 1,
          },
        };
      }),
});
