import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import { prisma } from '../index';
import { createError, asyncHandler } from '../middlewares/errorHandler';
import { AuthenticatedRequest } from '../middlewares/auth';
import { NotificationService } from '../services/notificationService';
import {
  createUserSchema,
  updateUserSchema,
  assignBotToUserSchema,
  assignTeachersToUserSchema,
  updateTeacherPermissionSchema,
  paginationSchema,
  userFilterSchema,
  uuidParamSchema,
  CreateUserInput,
  UpdateUserInput,
  AssignBotToUserInput,
  AssignTeachersToUserInput,
  UpdateTeacherPermissionInput,
  PaginationInput,
  UserFilterInput,
  UuidParamInput,
  adminSetPasswordSchema,
} from '../utils/validation';
import logger from '../utils/logger';

// Get all users with pagination and filtering
export const getAllUsers = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  // Validate query parameters
  const paginationData: PaginationInput = paginationSchema.parse(req.query);
  const filterData: UserFilterInput = userFilterSchema.parse(req.query);
  
  const { page, limit } = paginationData;
  const { role, search } = filterData;
  
  const skip = (page - 1) * limit;
  
  // Build where clause
  const where: any = {};
  
  if (role) {
    where.role = role;
  }
  
  // Teachers can only see students assigned to them
  if (req.user && req.user.role === 'TEACHER' && (role === 'STUDENT' || !role)) {
    where.assignedTeachers = {
      some: { teacherId: req.user.id },
    };
  }
  
  if (search) {
    where.OR = [
      { name: { contains: search, mode: 'insensitive' } },
      { email: { contains: search, mode: 'insensitive' } },
    ];
  }
  
  // Get users with pagination
  const [users, totalCount] = await Promise.all([
    prisma.user.findMany({
      where,
      select: {
        id: true,
        name: true,
        email: true,
        role: true,
        canCreateBots: true,
        canEditBots: true,
        profileImageUrl: true,
        emailVerified: true,
        assignedTeachers: {
          select: {
            teacher: {
              select: {
                id: true,
                name: true,
                email: true,
              },
            },
          },
        },
        createdAt: true,
        updatedAt: true,
        // Include bot accesses for students
        botAccesses: {
          select: {
            bot: {
              select: {
                id: true,
                name: true,
                topic: true,
                level: true,
              },
            },
            grantedAt: true,
          },
        },
      },
      skip,
      take: limit,
      orderBy: { createdAt: 'desc' },
    }),
    prisma.user.count({ where }),
  ]);
  
  const totalPages = Math.ceil(totalCount / limit);
  
  logger.info('Users retrieved successfully', {
    count: users.length,
    totalCount,
    page,
    limit,
    filters: filterData,
  });
  
  res.json({
    success: true,
    data: {
      users,
      pagination: {
        page,
        limit,
        totalCount,
        totalPages,
        hasNext: page < totalPages,
        hasPrev: page > 1,
      },
    },
  });
});

// Get user by ID
export const getUserById = asyncHandler(async (req: Request, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);
  
  const user = await prisma.user.findUnique({
    where: { id },
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      canCreateBots: true,
      canEditBots: true,
      profileImageUrl: true,
      emailVerified: true,
      assignedTeachers: {
        select: {
          teacher: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      },
      createdAt: true,
      updatedAt: true,
      // Include bot accesses for students
      botAccesses: {
        select: {
          bot: {
            select: {
              id: true,
              name: true,
              topic: true,
              level: true,
            },
          },
          grantedAt: true,
          granter: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      },
      // Include conversation count for students
      conversations: {
        select: {
          id: true,
          bot: {
            select: {
              id: true,
              name: true,
            },
          },
          startedAt: true,
          endedAt: true,
        },
        orderBy: { startedAt: 'desc' },
        take: 5, // Limit to recent conversations
      },
    },
  });
  
  if (!user) {
    throw createError('User not found', 404);
  }
  
  logger.info('User retrieved successfully', { userId: id });
  
  res.json({
    success: true,
    data: { user },
  });
});

// Create new user (admin/teacher)
export const createUser = asyncHandler(async (req: Request, res: Response) => {
  const validatedData: CreateUserInput = createUserSchema.parse(req.body);
  const { name, email, password, role, canCreateBots, canEditBots } = validatedData;

  // Check if user already exists
  const existingUser = await prisma.user.findUnique({
    where: { email },
  });

  if (existingUser) {
    throw createError('User with this email already exists', 409);
  }

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

  // Create user
  const user = await prisma.user.create({
    data: {
      name,
      email,
      password: hashedPassword,
      role,
      ...(role === 'TEACHER' && {
        canCreateBots: canCreateBots ?? false,
        canEditBots: canEditBots ?? false,
      }),
    },
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      profileImageUrl: true,
      emailVerified: true,
      canCreateBots: true,
      canEditBots: true,
      createdAt: true,
      updatedAt: true,
    },
  });
  
  logger.info('User created successfully', {
    userId: user.id,
    email: user.email,
    role: user.role,
  });
  
  res.status(201).json({
    success: true,
    message: 'User created successfully',
    data: { user },
  });
});

// Update user (admin only)
export const updateUser = asyncHandler(async (req: Request, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);
  const validatedData: UpdateUserInput = updateUserSchema.parse(req.body);
  const { name, email, role } = validatedData;
  
  // Check if user exists
  const existingUser = await prisma.user.findUnique({
    where: { id },
  });
  
  if (!existingUser) {
    throw createError('User not found', 404);
  }
  
  // Check if email is already taken by another user
  if (email && email !== existingUser.email) {
    const userWithEmail = await prisma.user.findUnique({
      where: { email },
    });
    
    if (userWithEmail) {
      throw createError('Email is already taken by another user', 409);
    }
  }
  
  // Update user
  const updatedUser = await prisma.user.update({
    where: { id },
    data: {
      ...(name && { name }),
      ...(email && { email }),
      ...(role && { role }),
    },
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      profileImageUrl: true,
      emailVerified: true,
      createdAt: true,
      updatedAt: true,
    },
  });
  
  logger.info('User updated successfully', {
    userId: id,
    updatedFields: Object.keys(validatedData),
  });
  
  res.json({
    success: true,
    message: 'User updated successfully',
    data: { user: updatedUser },
  });
});

// Admin: set password for a user
export const adminSetPassword = asyncHandler(async (req: Request, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);
  const { newPassword } = adminSetPasswordSchema.parse(req.body);

  // Ensure user exists
  const existingUser = await prisma.user.findUnique({ where: { id } });
  if (!existingUser) {
    throw createError('User not found', 404);
  }

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

  // Update password
  await prisma.user.update({ where: { id }, data: { password: hashedPassword } });

  // Invalidate refresh tokens for this user (force re-login if active elsewhere)
  await prisma.refreshToken.deleteMany({ where: { userId: id } });

  res.json({
    success: true,
    message: 'Password updated successfully',
  });
});

// Delete user (admin only)
export const deleteUser = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);

  if (!req.user) {
    throw createError('Authentication required', 401);
  }

  // Prevent self-deletion
  if (req.user.id === id) {
    throw createError('You cannot delete your own account', 400);
  }
  
  // Check if user exists
  const existingUser = await prisma.user.findUnique({
    where: { id },
    select: { id: true, email: true, role: true },
  });
  
  if (!existingUser) {
    throw createError('User not found', 404);
  }

  // Prevent deleting the last remaining admin
  if (existingUser.role === 'ADMIN') {
    const adminCount = await prisma.user.count({ where: { role: 'ADMIN' } });
    if (adminCount <= 1) {
      throw createError('Cannot delete the last admin user', 400);
    }
  }
  
  // Delete user (related data will be handled by database constraints)
  await prisma.user.delete({ where: { id } });
  
  logger.info('User deleted successfully', {
    userId: id,
    email: existingUser.email,
  });
  
  res.json({
    success: true,
    message: 'User deleted successfully',
  });
});

// Assign bot to student (admin/teacher)
export const assignBotToStudent = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);
  const validatedData: AssignBotToUserInput = assignBotToUserSchema.parse(req.body);
  const { botId, notes } = validatedData;
  
  if (!req.user) {
    throw createError('Authentication required', 401);
  }
  
  // Check if student exists
  const student = await prisma.user.findUnique({
    where: { id },
    select: { id: true, role: true },
  });
  
  if (!student) {
    throw createError('Student not found', 404);
  }
  
  if (student.role !== 'STUDENT') {
    throw createError('Can only assign bots to students', 400);
  }
  
  // Check if bot exists
  const bot = await prisma.bot.findUnique({
    where: { id: botId },
    select: { id: true, isActive: true },
  });
  
  if (!bot) {
    throw createError('Bot not found', 404);
  }
  
  if (!bot.isActive) {
    throw createError('Cannot assign inactive bot', 400);
  }
  
  // Check if access already exists
  const existingAccess = await prisma.studentBotAccess.findUnique({
    where: {
      studentId_botId: {
        studentId: id,
        botId,
      },
    },
  });
  
  if (existingAccess) {
    throw createError('Student already has access to this bot', 409);
  }
  
  // Grant access
  const botAccess = await prisma.studentBotAccess.create({
    data: {
      studentId: id,
      botId,
      grantedBy: req.user.id,
      notes: notes || null,
    },
    select: {
      id: true,
      grantedAt: true,
      bot: {
        select: {
          id: true,
          name: true,
          topic: true,
          level: true,
        },
      },
      granter: {
        select: {
          id: true,
          name: true,
          email: true,
        },
      },
    },
  });
  
  // Create notification for the student
  try {
    await NotificationService.createBotAssignmentNotification(
      id,
      botAccess.bot.name,
      botAccess.granter.name
    );
    logger.info('Notification created for bot assignment', {
      studentId: id,
      botName: botAccess.bot.name,
      grantedByName: botAccess.granter.name,
    });
  } catch (notificationError) {
    logger.error('Failed to create notification for bot assignment', {
      error: notificationError instanceof Error ? notificationError.message : 'Unknown error',
      studentId: id,
      botId,
    });
    // Don't fail the assignment if notification creation fails
  }

  logger.info('Bot assigned to student successfully', {
    studentId: id,
    botId,
    grantedBy: req.user.id,
  });
  
  res.json({
    success: true,
    message: 'Bot assigned to student successfully',
    data: { botAccess },
  });
});

// Update teacher permissions (admin only)
export const updateTeacherPermission = asyncHandler(async (req: Request, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);
  const validatedData: UpdateTeacherPermissionInput = updateTeacherPermissionSchema.parse(req.body);

  const existingUser = await prisma.user.findUnique({
    where: { id },
    select: { id: true, role: true, canCreateBots: true, canEditBots: true },
  });

  if (!existingUser) {
    throw createError('User not found', 404);
  }

  if (existingUser.role !== 'TEACHER') {
    throw createError('Can only update permissions for users with role TEACHER', 400);
  }

  const updateData: { canCreateBots?: boolean; canEditBots?: boolean } = {};
  if (validatedData.canCreateBots !== undefined) updateData.canCreateBots = validatedData.canCreateBots;
  if (validatedData.canEditBots !== undefined) updateData.canEditBots = validatedData.canEditBots;

  const updatedUser = await prisma.user.update({
    where: { id },
    data: updateData,
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      canCreateBots: true,
      canEditBots: true,
      profileImageUrl: true,
      emailVerified: true,
      createdAt: true,
      updatedAt: true,
    },
  });

  logger.info('Teacher permissions updated', {
    userId: id,
    canCreateBots: updatedUser.canCreateBots,
    canEditBots: updatedUser.canEditBots,
  });

  res.json({
    success: true,
    message: 'Teacher permissions updated successfully',
    data: { user: updatedUser },
  });
});

// Assign teachers to student (admin) - supports multiple teachers
export const assignTeachersToStudent = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { id }: UuidParamInput = uuidParamSchema.parse(req.params);
  const validatedData: AssignTeachersToUserInput = assignTeachersToUserSchema.parse(req.body);
  const { teacherIds } = validatedData;

  if (!req.user) {
    throw createError('Authentication required', 401);
  }

  // Check if student exists
  const student = await prisma.user.findUnique({
    where: { id },
    select: { id: true, role: true },
  });

  if (!student) {
    throw createError('Student not found', 404);
  }

  if (student.role !== 'STUDENT') {
    throw createError('Can only assign teachers to students', 400);
  }

  // Validate all teachers exist and have TEACHER role
  const uniqueTeacherIds = [...new Set(teacherIds)];
  for (const teacherId of uniqueTeacherIds) {
    const teacher = await prisma.user.findUnique({
      where: { id: teacherId },
      select: { id: true, role: true },
    });

    if (!teacher) {
      throw createError(`Teacher not found: ${teacherId}`, 404);
    }

    if (teacher.role !== 'TEACHER') {
      throw createError('Can only assign users with TEACHER role', 400);
    }
  }

  // Replace all assignments in a transaction
  const createOps = uniqueTeacherIds.map((teacherId) =>
    prisma.studentTeacherAssignment.create({
      data: { studentId: id, teacherId },
    })
  );
  await prisma.$transaction([
    prisma.studentTeacherAssignment.deleteMany({
      where: { studentId: id },
    }),
    ...createOps,
  ]);

  // Fetch updated student with teachers
  const updatedStudent = await prisma.user.findUnique({
    where: { id },
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      assignedTeachers: {
        select: {
          teacher: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      },
    },
  });

  logger.info('Teachers assigned to student', {
    studentId: id,
    teacherIds: uniqueTeacherIds,
    assignedBy: req.user.id,
  });

  res.json({
    success: true,
    message: uniqueTeacherIds.length > 0
      ? `${uniqueTeacherIds.length} teacher(s) assigned successfully`
      : 'All teacher assignments removed',
    data: {
      student: updatedStudent,
    },
  });
}); 