import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
import { z } from 'zod';
import { db } from '@/src/db/db_index'
import { staffUsers, staffRoles, users, userDetails, orders } from '@/src/db/schema'
import { eq, or, ilike, and, lt, desc } from 'drizzle-orm';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { ApiError } from '@/src/lib/api-error'

export const staffUserRouter = router({
  login: publicProcedure
    .input(z.object({
      name: z.string(),
      password: z.string(),
    }))
    .mutation(async ({ input }) => {
      const { name, password } = input;

      if (!name || !password) {
        throw new ApiError('Name and password are required', 400);
      }

      const staff = await db.query.staffUsers.findFirst({
        where: eq(staffUsers.name, name),
      });

      if (!staff) {
        throw new ApiError('Invalid credentials', 401);
      }

      const isPasswordValid = await bcrypt.compare(password, staff.password);
      if (!isPasswordValid) {
        throw new ApiError('Invalid credentials', 401);
      }

      const token = jwt.sign(
        { staffId: staff.id, name: staff.name },
        process.env.JWT_SECRET || 'default-secret',
        { expiresIn: '30d' }
      );

      return {
        message: 'Login successful',
        token,
        staff: { id: staff.id, name: staff.name },
      };
    }),

  getStaff: protectedProcedure
    .query(async ({ ctx }) => {
      const staff = await db.query.staffUsers.findMany({
        columns: {
          id: true,
          name: true,
        },
        with: {
          role: {
            with: {
              rolePermissions: {
                with: {
                  permission: true,
                },
              },
            },
          },
        },
      });

      // Transform the data to include role and permissions in a cleaner format
      const transformedStaff = staff.map((user) => ({
        id: user.id,
        name: user.name,
        role: user.role ? {
          id: user.role.id,
          name: user.role.roleName,
        } : null,
        permissions: user.role?.rolePermissions.map((rp) => ({
          id: rp.permission.id,
          name: rp.permission.permissionName,
        })) || [],
      }));

      return {
        staff: transformedStaff,
      };
    }),

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

      let whereCondition = undefined;

      if (search) {
        whereCondition = or(
          ilike(users.name, `%${search}%`),
          ilike(users.email, `%${search}%`),
          ilike(users.mobile, `%${search}%`)
        );
      }

      if (cursor) {
        const cursorCondition = lt(users.id, cursor);
        whereCondition = whereCondition ? and(whereCondition, cursorCondition) : cursorCondition;
      }

      const allUsers = await db.query.users.findMany({
        where: whereCondition,
        with: {
          userDetails: true,
        },
        orderBy: desc(users.id),
        limit: limit + 1, // fetch one extra to check if there's more
      });

      const hasMore = allUsers.length > limit;
      const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers;

      const formattedUsers = usersToReturn.map(user => ({
        id: user.id,
        name: user.name,
        email: user.email,
        mobile: user.mobile,
        image: user.userDetails?.profileImage || null,
      }));

      return {
        users: formattedUsers,
        nextCursor: hasMore ? usersToReturn[usersToReturn.length - 1].id : undefined,
      };
    }),

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

      const user = await db.query.users.findFirst({
        where: eq(users.id, userId),
        with: {
          userDetails: true,
          orders: {
            orderBy: desc(orders.createdAt),
            limit: 1,
          },
        },
      });

      if (!user) {
        throw new ApiError("User not found", 404);
      }

      const lastOrder = user.orders[0];

      return {
        id: user.id,
        name: user.name,
        email: user.email,
        mobile: user.mobile,
        addedOn: user.createdAt,
        lastOrdered: lastOrder?.createdAt || null,
        isSuspended: user.userDetails?.isSuspended || false,
      };
    }),

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

      await db
        .insert(userDetails)
        .values({ userId, isSuspended })
        .onConflictDoUpdate({
          target: userDetails.userId,
          set: { isSuspended },
        });

      return { success: true };
    }),

  createStaffUser: protectedProcedure
    .input(z.object({
      name: z.string().min(1, 'Name is required'),
      password: z.string().min(6, 'Password must be at least 6 characters'),
      roleId: z.number().int().positive('Role is required'),
    }))
    .mutation(async ({ input, ctx }) => {
      const { name, password, roleId } = input;

      // Check if staff user already exists
      const existingUser = await db.query.staffUsers.findFirst({
        where: eq(staffUsers.name, name),
      });

      if (existingUser) {
        throw new ApiError('Staff user with this name already exists', 409);
      }

      // Check if role exists
      const role = await db.query.staffRoles.findFirst({
        where: eq(staffRoles.id, roleId),
      });

      if (!role) {
        throw new ApiError('Invalid role selected', 400);
      }

      // Hash password
      const hashedPassword = await bcrypt.hash(password, 12);

      // Create staff user
      const [newUser] = await db.insert(staffUsers).values({
        name: name.trim(),
        password: hashedPassword,
        staffRoleId: roleId,
      }).returning();

      return { success: true, user: { id: newUser.id, name: newUser.name } };
    }),

  getRoles: protectedProcedure
    .query(async ({ ctx }) => {
      const roles = await db.query.staffRoles.findMany({
        columns: {
          id: true,
          roleName: true,
        },
      });

      return {
        roles: roles.map(role => ({
          id: role.id,
          name: role.roleName,
        })),
      };
    }),
});