import { router, protectedProcedure } from '@/src/trpc/trpc-index'
import { z } from 'zod';
import dayjs from 'dayjs';
import {
  // Batch 1 - Non-transaction methods
  getAllCoupons as getAllCouponsFromDb,
  getCouponById as getCouponByIdFromDb,
  invalidateCoupon as invalidateCouponInDb,
  validateCoupon as validateCouponInDb,
  getReservedCoupons as getReservedCouponsFromDb,
  getUsersForCoupon as getUsersForCouponFromDb,
  // Batch 2 - Transaction methods
  createCouponWithRelations,
  updateCouponWithRelations,
  generateCancellationCoupon,
  createReservedCouponWithProducts,
  createCouponForUser,
  checkUsersExist,
  checkCouponExists,
  checkReservedCouponExists,
  getOrderWithUser,
} from '@/src/dbService'
import type { Coupon, CouponValidationResult, UserMiniInfo } from '@packages/shared'

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 }): Promise<Coupon> => {
      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");
      }

      // 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) {
        const timestamp = Date.now().toString().slice(-6);
        const random = Math.random().toString(36).substring(2, 8).toUpperCase();
        finalCouponCode = `MF${timestamp}${random}`;
      }

      // Using dbService helper (new implementation)
      const codeExists = await checkCouponExists(finalCouponCode);
      if (codeExists) {
        throw new Error("Coupon code already exists");
      }

      // If applicableUsers is provided, verify users exist
      if (applicableUsers && applicableUsers.length > 0) {
        const usersExist = await checkUsersExist(applicableUsers);
        if (!usersExist) {
          throw new Error("Some applicable users not found");
        }
      }

      const coupon = await createCouponWithRelations(
        {
          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,
          exclusiveApply: exclusiveApply || false,
        },
        applicableUsers,
        applicableProducts
      );

      /*
      // Old implementation - direct DB query with transaction:
      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,
        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 }): Promise<{ coupons: any[]; nextCursor?: number }> => {
      const { cursor, limit, search } = input;
      
      const { coupons: couponsList, hasMore } = await getAllCouponsFromDb(cursor, limit, search);

      const nextCursor = hasMore ? couponsList[couponsList.length - 1].id : undefined;
      
      return { coupons: couponsList, nextCursor };
    }),

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

      const result = await getCouponByIdFromDb(couponId);

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

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

  update: protectedProcedure
    .input(z.object({
      id: z.number(),
      updates: createCouponBodySchema.extend({
        isInvalidated: z.boolean().optional(),
      }),
    }))
    .mutation(async ({ input }): Promise<Coupon> => {
      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");
        }
      }

      // Prepare update data
      const updateData: any = {};
      if (updates.couponCode !== undefined) updateData.couponCode = updates.couponCode;
      if (updates.isUserBased !== undefined) updateData.isUserBased = updates.isUserBased;
      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.isApplyForAll !== undefined) updateData.isApplyForAll = updates.isApplyForAll;
      if (updates.validTill !== undefined) updateData.validTill = updates.validTill ? dayjs(updates.validTill).toDate() : null;
      if (updates.maxLimitForUser !== undefined) updateData.maxLimitForUser = updates.maxLimitForUser;
      if (updates.exclusiveApply !== undefined) updateData.exclusiveApply = updates.exclusiveApply;
      if (updates.isInvalidated !== undefined) updateData.isInvalidated = updates.isInvalidated;
      if (updates.productIds !== undefined) updateData.productIds = updates.productIds;

      // Using dbService helper (new implementation)
      const coupon = await updateCouponWithRelations(
        id,
        updateData,
        updates.applicableUsers,
        updates.applicableProducts
      );

      /*
      // Old implementation - direct DB query:
      const result = await db.update(coupons)
        .set(updateData)
        .where(eq(coupons.id, id))
        .returning();

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

      // 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 coupon;
    }),

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

      await invalidateCouponInDb(id);

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

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

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

      const result = await validateCouponInDb(code, userId, orderAmount);

      return result;
    }),

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

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

      // Using dbService helper (new implementation)
      const order = await getOrderWithUser(orderId);

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

      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 codeExists = await checkCouponExists(couponCode);
      if (codeExists) {
        throw new Error("Coupon code already exists");
      }

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

      const coupon = await generateCancellationCoupon(
        orderId,
        staffUserId,
        order.userId,
        orderAmount,
        couponCode
      );

      /*
      // Old implementation - direct DB query with transaction:
      const coupon = await db.transaction(async (tx) => {
        // Calculate expiry date (30 days from now)
        const expiryDate = new Date();
        expiryDate.setDate(expiryDate.getDate() + 30);

        // 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 }): Promise<{ coupons: any[]; nextCursor?: number }> => {
      const { cursor, limit, search } = input;

      const { coupons: result, hasMore } = await getReservedCouponsFromDb(cursor, limit, search);

      const nextCursor = hasMore ? result[result.length - 1].id : undefined;

      return {
        coupons: result,
        nextCursor,
      };
    }),

  createReservedCoupon: protectedProcedure
    .input(createCouponBodySchema)
    .mutation(async ({ input, ctx }): Promise<any> => {
      const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableProducts, maxValue, 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)");
      }

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

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

      // Using dbService helper (new implementation)
      const codeExists = await checkReservedCouponExists(secretCode);
      if (codeExists) {
        throw new Error("Secret code already exists");
      }

      const coupon = await createReservedCouponWithProducts(
        {
          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,
        },
        applicableProducts
      );

      /*
      // Old implementation - direct DB query:
      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 }): Promise<{ users: UserMiniInfo[] }> => {
      const { search, limit, offset } = input;

      const result = await getUsersForCouponFromDb(search, limit, offset);

      return result;
    }),

  createCoupon: protectedProcedure
    .input(z.object({
      mobile: z.string().min(1, 'Mobile number is required'),
    }))
    .mutation(async ({ input, ctx }): Promise<{ success: boolean; coupon: any }> => {
      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");
      }

      // 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}`;

      // Using dbService helper (new implementation)
      const codeExists = await checkCouponExists(couponCode);
      if (codeExists) {
        throw new Error("Generated coupon code already exists - please try again");
      }

      const { coupon, user } = await createCouponForUser(cleanMobile, couponCode, staffUserId);

      /*
      // Old implementation - direct DB query with transaction:
      // Check if user exists, create if not
      let user = await db.query.users.findFirst({
        where: eq(users.mobile, cleanMobile),
      });

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

      // Create the coupon
      const [coupon] = await db.insert(coupons).values({
        couponCode,
        isUserBased: true,
        discountPercent: "20",
        minOrder: "1000",
        maxValue: "500",
        maxLimitForUser: 1,
        isApplyForAll: false,
        exclusiveApply: false,
        createdBy: staffUserId,
        validTill: dayjs().add(90, 'days').toDate(),
      }).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,
        },
      };
    }),
});
