import { protectedProcedure } from '@/src/trpc/trpc-index';
import { z } from 'zod';
import { db } from '@/src/db/db_index';
import { users, complaints, orders, orderItems, notifCreds, unloggedUserTokens, userDetails, userIncidents } from '@/src/db/schema';
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
import { ApiError } from '@/src/lib/api-error';
import { notificationQueue } from '@/src/lib/notif-job';
import { recomputeUserNegativityScore } from '@/src/stores/user-negativity-store';

async function createUserByMobile(mobile: string): Promise<typeof users.$inferSelect> {
  // Clean mobile number (remove non-digits)
  const cleanMobile = mobile.replace(/\D/g, '');

  // Validate: exactly 10 digits
  if (cleanMobile.length !== 10) {
    throw new ApiError('Mobile number must be exactly 10 digits', 400);
  }

  // Check if user already exists
  const [existingUser] = await db
    .select()
    .from(users)
    .where(eq(users.mobile, cleanMobile))
    .limit(1);

  if (existingUser) {
    throw new ApiError('User with this mobile number already exists', 409);
  }

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

  return newUser;
}

export const userRouter = {
  createUserByMobile: protectedProcedure
    .input(z.object({
      mobile: z.string().min(1, 'Mobile number is required'),
    }))
    .mutation(async ({ input }) => {
      const newUser = await createUserByMobile(input.mobile);

      return {
        success: true,
        data: newUser,
      };
    }),

  getEssentials: protectedProcedure
    .query(async () => {
      const count = await db.$count(complaints, eq(complaints.isResolved, false));
      
      return {
        unresolvedComplaints: count || 0,
      };
    }),

  getAllUsers: protectedProcedure
    .input(z.object({
      limit: z.number().min(1).max(100).default(50),
      cursor: z.number().optional(),
      search: z.string().optional(),
    }))
    .query(async ({ input }) => {
      const { limit, cursor, search } = input;
      
      // Build where conditions
      const whereConditions = [];
      
      if (search && search.trim()) {
        whereConditions.push(sql`${users.mobile} ILIKE ${`%${search.trim()}%`}`);
      }
      
      if (cursor) {
        whereConditions.push(sql`${users.id} > ${cursor}`);
      }

      // Get users with filters applied
      const usersList = await db
        .select({
          id: users.id,
          name: users.name,
          mobile: users.mobile,
          createdAt: users.createdAt,
        })
        .from(users)
        .where(whereConditions.length > 0 ? sql.join(whereConditions, sql` AND `) : undefined)
        .orderBy(asc(users.id))
        .limit(limit + 1); // Get one extra to determine if there's more

      // Check if there are more results
      const hasMore = usersList.length > limit;
      const usersToReturn = hasMore ? usersList.slice(0, limit) : usersList;

      // Get order stats for each user
      const userIds = usersToReturn.map(u => u.id);
      
      let orderCounts: { userId: number; totalOrders: number }[] = [];
      let lastOrders: { userId: number; lastOrderDate: Date | null }[] = [];
      let suspensionStatuses: { userId: number; isSuspended: boolean }[] = [];
      
      if (userIds.length > 0) {
        // Get total orders per user
        orderCounts = await db
          .select({
            userId: orders.userId,
            totalOrders: count(orders.id),
          })
          .from(orders)
          .where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
          .groupBy(orders.userId);

        // Get last order date per user
        lastOrders = await db
          .select({
            userId: orders.userId,
            lastOrderDate: max(orders.createdAt),
          })
          .from(orders)
          .where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
          .groupBy(orders.userId);

        // Get suspension status for each user
        suspensionStatuses = await db
          .select({
            userId: userDetails.userId,
            isSuspended: userDetails.isSuspended,
          })
          .from(userDetails)
          .where(sql`${userDetails.userId} IN (${sql.join(userIds, sql`, `)})`);
      }

      // Create lookup maps
      const orderCountMap = new Map(orderCounts.map(o => [o.userId, o.totalOrders]));
      const lastOrderMap = new Map(lastOrders.map(o => [o.userId, o.lastOrderDate]));
      const suspensionMap = new Map(suspensionStatuses.map(s => [s.userId, s.isSuspended]));

      // Combine data
      const usersWithStats = usersToReturn.map(user => ({
        ...user,
        totalOrders: orderCountMap.get(user.id) || 0,
        lastOrderDate: lastOrderMap.get(user.id) || null,
        isSuspended: suspensionMap.get(user.id) ?? false,
      }));

      // Get next cursor
      const nextCursor = hasMore ? usersToReturn[usersToReturn.length - 1].id : undefined;

      return {
        users: usersWithStats,
        nextCursor,
        hasMore,
      };
    }),

  getUserDetails: protectedProcedure
    .input(z.object({
      userId: z.number(),
    }))
    .query(async ({ input }) => {
      const { userId } = input;

      // Get user info
      const user = await db
        .select({
          id: users.id,
          name: users.name,
          mobile: users.mobile,
          createdAt: users.createdAt,
        })
        .from(users)
        .where(eq(users.id, userId))
        .limit(1);

      if (!user || user.length === 0) {
        throw new ApiError('User not found', 404);
      }

      // Get user suspension status
      const userDetail = await db
        .select({
          isSuspended: userDetails.isSuspended,
        })
        .from(userDetails)
        .where(eq(userDetails.userId, userId))
        .limit(1);

      // Get all orders for this user with order items count
      const userOrders = await db
        .select({
          id: orders.id,
          readableId: orders.readableId,
          totalAmount: orders.totalAmount,
          createdAt: orders.createdAt,
          isFlashDelivery: orders.isFlashDelivery,
        })
        .from(orders)
        .where(eq(orders.userId, userId))
        .orderBy(desc(orders.createdAt));

      // Get order status for each order
      const orderIds = userOrders.map(o => o.id);
      
      let orderStatuses: { orderId: number; isDelivered: boolean; isCancelled: boolean }[] = [];
      
      if (orderIds.length > 0) {
        const { orderStatus } = await import('@/src/db/schema');
        orderStatuses = await db
          .select({
            orderId: orderStatus.orderId,
            isDelivered: orderStatus.isDelivered,
            isCancelled: orderStatus.isCancelled,
          })
          .from(orderStatus)
          .where(sql`${orderStatus.orderId} IN (${sql.join(orderIds, sql`, `)})`);
      }

      // Get item counts for each order
      const itemCounts = await db
        .select({
          orderId: orderItems.orderId,
          itemCount: count(orderItems.id),
        })
        .from(orderItems)
        .where(sql`${orderItems.orderId} IN (${sql.join(orderIds, sql`, `)})`)
        .groupBy(orderItems.orderId);

      // Create lookup maps
      const statusMap = new Map(orderStatuses.map(s => [s.orderId, s]));
      const itemCountMap = new Map(itemCounts.map(c => [c.orderId, c.itemCount]));

      // Determine status string
      const getStatus = (status: { isDelivered: boolean; isCancelled: boolean } | undefined) => {
        if (!status) return 'pending';
        if (status.isCancelled) return 'cancelled';
        if (status.isDelivered) return 'delivered';
        return 'pending';
      };

      // Combine data
      const ordersWithDetails = userOrders.map(order => {
        const status = statusMap.get(order.id);
        return {
          id: order.id,
          readableId: order.readableId,
          totalAmount: order.totalAmount,
          createdAt: order.createdAt,
          isFlashDelivery: order.isFlashDelivery,
          status: getStatus(status),
          itemCount: itemCountMap.get(order.id) || 0,
        };
      });

      return {
        user: {
          ...user[0],
          isSuspended: userDetail[0]?.isSuspended ?? false,
        },
        orders: ordersWithDetails,
      };
    }),

  updateUserSuspension: protectedProcedure
    .input(z.object({
      userId: z.number(),
      isSuspended: z.boolean(),
    }))
    .mutation(async ({ input }) => {
      const { userId, isSuspended } = input;

      // Check if user exists
      const user = await db
        .select({ id: users.id })
        .from(users)
        .where(eq(users.id, userId))
        .limit(1);

      if (!user || user.length === 0) {
        throw new ApiError('User not found', 404);
      }

      // Check if user_details record exists
      const existingDetail = await db
        .select({ id: userDetails.id })
        .from(userDetails)
        .where(eq(userDetails.userId, userId))
        .limit(1);

      if (existingDetail.length > 0) {
        // Update existing record
        await db
          .update(userDetails)
          .set({ isSuspended })
          .where(eq(userDetails.userId, userId));
      } else {
        // Insert new record
        await db
          .insert(userDetails)
          .values({
            userId,
            isSuspended,
          });
      }

      return {
        success: true,
        message: `User ${isSuspended ? 'suspended' : 'unsuspended'} successfully`,
      };
    }),

  getUsersForNotification: protectedProcedure
    .input(z.object({
      search: z.string().optional(),
    }))
    .query(async ({ input }) => {
      const { search } = input;

      // Get all users
      let usersList;
      if (search && search.trim()) {
        usersList = await db
          .select({
            id: users.id,
            name: users.name,
            mobile: users.mobile,
          })
          .from(users)
          .where(sql`${users.mobile} ILIKE ${`%${search.trim()}%`} OR ${users.name} ILIKE ${`%${search.trim()}%`}`);
      } else {
        usersList = await db
          .select({
            id: users.id,
            name: users.name,
            mobile: users.mobile,
          })
          .from(users);
      }

      // Get eligible users (have notif_creds entry)
      const eligibleUsers = await db
        .select({ userId: notifCreds.userId })
        .from(notifCreds);

      const eligibleSet = new Set(eligibleUsers.map(u => u.userId));

      return {
        users: usersList.map(user => ({
          id: user.id,
          name: user.name,
          mobile: user.mobile,
          isEligibleForNotif: eligibleSet.has(user.id),
        })),
      };
    }),

  sendNotification: protectedProcedure
    .input(z.object({
      userIds: z.array(z.number()).default([]),
      title: z.string().min(1, 'Title is required'),
      text: z.string().min(1, 'Message is required'),
      imageUrl: z.string().optional(),
    }))
    .mutation(async ({ input }) => {
      const { userIds, title, text, imageUrl } = input;

      let tokens: string[] = [];

      if (userIds.length === 0) {
        // Send to all users - get tokens from both logged-in and unlogged users
        const loggedInTokens = await db.select({ token: notifCreds.token }).from(notifCreds);
        const unloggedTokens = await db.select({ token: unloggedUserTokens.token }).from(unloggedUserTokens);
        
        tokens = [
          ...loggedInTokens.map(t => t.token),
          ...unloggedTokens.map(t => t.token)
        ];
      } else {
        // Send to specific users - get their tokens
        const userTokens = await db
          .select({ token: notifCreds.token })
          .from(notifCreds)
          .where(inArray(notifCreds.userId, userIds));
        
        tokens = userTokens.map(t => t.token);
      }

      // Queue one job per token
      let queuedCount = 0;
      for (const token of tokens) {
        try {
          await notificationQueue.add('send-admin-notification', {
            token,
            title,
            body: text,
            imageUrl: imageUrl || null,
          }, {
            attempts: 3,
            backoff: {
              type: 'exponential',
              delay: 2000,
            },
          });
          queuedCount++;
        } catch (error) {
          console.error(`Failed to queue notification for token:`, error);
        }
      }

      return {
        success: true,
        message: `Notification queued for ${queuedCount} users`,
      };
    }),

  getUserIncidents: protectedProcedure
    .input(z.object({
      userId: z.number(),
    }))
    .query(async ({ input }) => {
      const { userId } = input;

      const incidents = await db.query.userIncidents.findMany({
        where: eq(userIncidents.userId, userId),
        with: {
          order: {
            with: {
              orderStatus: true,
            },
          },
          addedBy: true,
        },
        orderBy: desc(userIncidents.dateAdded),
      });

      return {
        incidents: incidents.map(incident => ({
          id: incident.id,
          userId: incident.userId,
          orderId: incident.orderId,
          dateAdded: incident.dateAdded,
          adminComment: incident.adminComment,
          addedBy: incident.addedBy?.name || 'Unknown',
          negativityScore: incident.negativityScore,
          orderStatus: incident.order?.orderStatus?.[0]?.isCancelled ? 'cancelled' : 'active',
        })),
      };
    }),

  addUserIncident: protectedProcedure
    .input(z.object({
      userId: z.number(),
      orderId: z.number().optional(),
      adminComment: z.string().optional(),
      negativityScore: z.number().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const { userId, orderId, adminComment, negativityScore } = input;
      
      const adminUserId = ctx.staffUser?.id;
      
      if (!adminUserId) {
        throw new ApiError('Admin user not authenticated', 401);
      }


      const incidentObj = { userId, orderId, adminComment, addedBy: adminUserId, negativityScore };
      
      const [incident] = await db.insert(userIncidents)
        .values({
          ...incidentObj,
        })
        .returning();

      recomputeUserNegativityScore(userId);

      return {
        success: true,
        data: incident,
      };
    }),
};
