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 {
  loginSchema,
  registerSchema,
  refreshTokenSchema,
  changePasswordSchema,
  updateProfileSchema,
  LoginInput,
  RegisterInput,
  RefreshTokenInput,
  ChangePasswordInput,
  UpdateProfileInput,
} from '../utils/validation';
import {
  generateAccessToken,
  generateRefreshToken,
  verifyRefreshToken,
  getTokenExpirationTime,
} from '../utils/jwt';
import logger from '../utils/logger';

export const register = asyncHandler(async (req: Request, res: Response) => {
  const startTime = Date.now();
  
  logger.info('👤 User registration attempt', {
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    origin: req.get('Origin'),
    contentType: req.get('Content-Type')
  });

  const validatedData: RegisterInput = registerSchema.parse(req.body);
  const { name, email, password, role } = validatedData;

  logger.info('✅ Registration data validated', {
    email,
    role,
    nameLength: name.length,
    passwordLength: password.length,
    hasPassword: !!password
  });

  // Check if user already exists
  logger.info('🔍 Checking for existing user', { email });
  
  const existingUser = await prisma.user.findUnique({
    where: { email },
  });

  if (existingUser) {
    logger.warn('❌ Registration failed - user already exists', {
      email,
      existingUserId: existingUser.id,
      ip: req.ip
    });
    throw createError('User with this email already exists', 409);
  }

  logger.info('✅ User does not exist, proceeding with registration');

  // Hash password
  const saltRounds = 12;
  logger.info('🔐 Hashing password', { saltRounds });
  
  const hashedPassword = await bcrypt.hash(password, saltRounds);

  logger.info('✅ Password hashed successfully');

  // Create user
  logger.info('📝 Creating user in database', { email, role });
  
  const user = await prisma.user.create({
    data: {
      name,
      email,
      password: hashedPassword,
      role,
    },
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      createdAt: true,
    },
  });

  const responseTime = Date.now() - startTime;

  logger.info('🎉 User registered successfully', {
    userId: user.id,
    email: user.email,
    role: user.role,
    responseTime: `${responseTime}ms`,
    ip: req.ip
  });

  res.status(201).json({
    success: true,
    message: 'User registered successfully',
    data: { user },
  });
});

export const login = asyncHandler(async (req: Request, res: Response) => {
  const startTime = Date.now();
  
  logger.info('🔐 User login attempt', {
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    origin: req.get('Origin'),
    contentType: req.get('Content-Type')
  });

  const validatedData: LoginInput = loginSchema.parse(req.body);
  const { email, password } = validatedData;

  logger.info('✅ Login data validated', {
    email,
    passwordLength: password.length,
    hasPassword: !!password
  });

  // Find user
  logger.info('🔍 Searching for user by email', { email });
  
  const user = await prisma.user.findUnique({
    where: { email },
    select: {
      id: true,
      name: true,
      email: true,
      password: true,
      role: true,
      canEditBots: true,
      canCreateBots: true,
    },
  });

  if (!user) {
    logger.warn('❌ Login failed - user not found', {
      email,
      ip: req.ip,
      userAgent: req.get('User-Agent')
    });
    throw createError('Invalid email or password', 401);
  }

  logger.info('✅ User found in database', {
    userId: user.id,
    email: user.email,
    role: user.role
  });

  // Verify password
  logger.info('🔐 Verifying password');
  
  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    logger.warn('❌ Login failed - invalid password', {
      userId: user.id,
      email: user.email,
      ip: req.ip,
      userAgent: req.get('User-Agent')
    });
    throw createError('Invalid email or password', 401);
  }

  logger.info('✅ Password verified successfully', { userId: user.id });

  // Generate tokens
  const tokenPayload = {
    userId: user.id,
    email: user.email,
    role: user.role,
  };

  logger.info('🎫 Generating JWT tokens', {
    userId: user.id,
    email: user.email,
    role: user.role
  });

  const accessToken = generateAccessToken(tokenPayload);
  const refreshToken = generateRefreshToken(tokenPayload);

  logger.info('✅ JWT tokens generated successfully', {
    userId: user.id,
    accessTokenLength: accessToken.length,
    refreshTokenLength: refreshToken.length
  });

  // Store refresh token in database
  const refreshTokenExpiry = getTokenExpirationTime(
    process.env.JWT_REFRESH_EXPIRES_IN || '7d'
  );

  logger.info('💾 Storing refresh token in database', {
    userId: user.id,
    expiresAt: refreshTokenExpiry
  });

  await prisma.refreshToken.create({
    data: {
      token: refreshToken,
      userId: user.id,
      expiresAt: refreshTokenExpiry,
    },
  });

  logger.info('✅ Refresh token stored in database', { userId: user.id });

  // Set cookies
  const isProduction = process.env.NODE_ENV === 'production';
  
  logger.info('🍪 Setting authentication cookies', {
    userId: user.id,
    isProduction,
    secure: isProduction,
    sameSite: isProduction ? 'strict' : 'lax'
  });
  
  res.cookie('accessToken', accessToken, {
    httpOnly: true,
    secure: isProduction,
    sameSite: isProduction ? 'strict' : 'lax',
    maxAge: 15 * 60 * 1000, // 15 minutes
  });

  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: isProduction,
    sameSite: isProduction ? 'strict' : 'lax',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
  });

  const responseTime = Date.now() - startTime;

  logger.info('🎉 User logged in successfully', {
    userId: user.id,
    email: user.email,
    role: user.role,
    responseTime: `${responseTime}ms`,
    ip: req.ip
  });

  res.json({
    success: true,
    message: 'Login successful',
    data: {
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
        role: user.role,
        canEditBots: user.role === 'TEACHER' ? (user.canEditBots ?? false) : undefined,
        canCreateBots: user.role === 'TEACHER' ? (user.canCreateBots ?? false) : undefined,
      },
      token: accessToken,
    },
  });
});

export const refreshToken = asyncHandler(async (req: Request, res: Response) => {
  const startTime = Date.now();
  
  logger.info('🔄 Token refresh attempt', {
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    hasRefreshToken: !!req.cookies.refreshToken,
    hasRefreshTokenInBody: !!req.body?.refreshToken,
    origin: req.get('Origin'),
    referer: req.get('Referer')
  });

  // Prefer refresh token from httpOnly cookie; fallback to body for compatibility
  let token: string | undefined = req.cookies?.refreshToken;
  
  // Validación temprana sin Zod para fail-fast
  if (!token && !req.body?.refreshToken) {
    const totalTime = Date.now() - startTime;
    logger.warn('❌ Refresh failed - no refresh token provided (fail-fast)', {
      ip: req.ip,
      userAgent: req.get('User-Agent'),
      origin: req.get('Origin'),
      referer: req.get('Referer'),
      totalTime: `${totalTime}ms`,
      hasBody: !!req.body && Object.keys(req.body).length > 0,
      hasCookies: !!req.cookies && Object.keys(req.cookies).length > 0
    });
    throw createError('Refresh token is required', 401);
  }
  
  // Si no hay token en cookie, intentar validar el body con Zod
  if (!token) {
    const validatedData: RefreshTokenInput = refreshTokenSchema.parse(req.body);
    token = validatedData.refreshToken;
  }

  // Esta validación ahora es redundante pero se mantiene por seguridad
  if (!token) {
    logger.warn('❌ Refresh failed - no refresh token after validation', {
      ip: req.ip,
      userAgent: req.get('User-Agent')
    });
    throw createError('Refresh token is required', 401);
  }

  logger.info('✅ Refresh token provided, attempting verification');

  try {
    // Verify refresh token
    const decoded = verifyRefreshToken(token);
    
    logger.info('✅ Refresh token verified', {
      userId: decoded.userId,
      email: decoded.email,
      role: decoded.role
    });

    // Check if token exists in database
    const storedToken = await prisma.refreshToken.findUnique({
      where: { token }
    });

    if (!storedToken) {
      logger.warn('❌ Refresh failed - token not found in database', {
        tokenLength: token.length,
        ip: req.ip
      });
      throw createError('Invalid refresh token', 401);
    }

    if (storedToken.expiresAt < new Date()) {
      logger.warn('❌ Refresh failed - token expired', {
        userId: storedToken.userId,
        expiresAt: storedToken.expiresAt,
        ip: req.ip
      });
      
      // Remove expired token
      await prisma.refreshToken.delete({
        where: { token },
      });
      
      throw createError('Refresh token expired', 401);
    }

    logger.info('✅ Stored token validated', {
      userId: storedToken.userId,
      expiresAt: storedToken.expiresAt
    });

    // Load user data
    const storedUser = await prisma.user.findUnique({
      where: { id: storedToken.userId },
      select: {
        id: true,
        email: true,
        name: true,
        role: true,
        canEditBots: true,
        canCreateBots: true,
      },
    });
    if (!storedUser) {
      logger.warn('❌ Refresh failed - user not found for stored token', { userId: storedToken.userId });
      throw createError('User not found', 404);
    }

    // Generate new tokens
    const tokenPayload = {
      userId: storedUser.id,
      email: storedUser.email,
      role: storedUser.role,
    };

    const newAccessToken = generateAccessToken(tokenPayload);
    const newRefreshToken = generateRefreshToken(tokenPayload);

    logger.info('🎫 New tokens generated', {
      userId: storedUser.id,
      newAccessTokenLength: newAccessToken.length,
      newRefreshTokenLength: newRefreshToken.length
    });

    // Update refresh token in database
    const newRefreshTokenExpiry = getTokenExpirationTime(
      process.env.JWT_REFRESH_EXPIRES_IN || '7d'
    );

    await prisma.refreshToken.update({
      where: { token },
      data: {
        token: newRefreshToken,
        expiresAt: newRefreshTokenExpiry,
      },
    });

    logger.info('✅ Refresh token updated in database', {
      userId: storedUser.id,
      newExpiresAt: newRefreshTokenExpiry
    });

    // Set new cookies
    const isProduction = process.env.NODE_ENV === 'production';
    
    res.cookie('accessToken', newAccessToken, {
      httpOnly: true,
      secure: isProduction,
      sameSite: isProduction ? 'strict' : 'lax',
      maxAge: 15 * 60 * 1000, // 15 minutes
    });

    res.cookie('refreshToken', newRefreshToken, {
      httpOnly: true,
      secure: isProduction,
      sameSite: isProduction ? 'strict' : 'lax',
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    });

    const responseTime = Date.now() - startTime;

    logger.info('🎉 Token refresh successful', {
      userId: storedUser.id,
      email: storedUser.email,
      role: storedUser.role,
      responseTime: `${responseTime}ms`,
      ip: req.ip
    });

    res.json({
      success: true,
      message: 'Token refreshed successfully',
      data: {
        user: {
          id: storedUser.id,
          name: storedUser.name,
          email: storedUser.email,
          role: storedUser.role,
          canEditBots: storedUser.role === 'TEACHER' ? (storedUser.canEditBots ?? false) : undefined,
          canCreateBots: storedUser.role === 'TEACHER' ? (storedUser.canCreateBots ?? false) : undefined,
        },
      },
    });
  } catch (error) {
    const totalTime = Date.now() - startTime;
    
    logger.error('❌ Token refresh failed', {
      error: error instanceof Error ? error.message : 'Unknown error',
      ip: req.ip,
      userAgent: req.get('User-Agent'),
      totalTime: `${totalTime}ms`
    });
    throw error;
  }
});

export const logout = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const startTime = Date.now();
  
  logger.info('🚪 User logout attempt', {
    userId: req.user?.id,
    email: req.user?.email,
    role: req.user?.role,
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });

  // Clear cookies
  res.clearCookie('accessToken');
  res.clearCookie('refreshToken');

  logger.info('🍪 Authentication cookies cleared');

  // Remove refresh token from database if it exists
  const refreshToken = req.cookies.refreshToken;
  if (refreshToken) {
    logger.info('🗑️ Removing refresh token from database', {
      userId: req.user?.id,
      tokenLength: refreshToken.length
    });

    try {
      await prisma.refreshToken.deleteMany({
        where: { token: refreshToken },
      });

      logger.info('✅ Refresh token removed from database', {
        userId: req.user?.id
      });
    } catch (error) {
      logger.warn('⚠️ Failed to remove refresh token from database', {
        userId: req.user?.id,
        error: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  const responseTime = Date.now() - startTime;

  logger.info('🎉 User logged out successfully', {
    userId: req.user?.id,
    email: req.user?.email,
    role: req.user?.role,
    responseTime: `${responseTime}ms`,
    ip: req.ip
  });

  res.json({
    success: true,
    message: 'Logout successful',
  });
});

export const changePassword = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const startTime = Date.now();
  
  logger.info('🔐 Password change attempt', {
    userId: req.user?.id,
    email: req.user?.email,
    role: req.user?.role,
    ip: req.ip
  });

  const validatedData: ChangePasswordInput = changePasswordSchema.parse(req.body);
  const { currentPassword, newPassword } = validatedData;

  logger.info('✅ Password change data validated', {
    userId: req.user?.id,
    currentPasswordLength: currentPassword.length,
    newPasswordLength: newPassword.length
  });

  // Get user with current password
  const user = await prisma.user.findUnique({
    where: { id: req.user!.id },
    select: {
      id: true,
      email: true,
      password: true,
      role: true,
    },
  });

  if (!user) {
    logger.error('❌ Password change failed - user not found', {
      userId: req.user?.id,
      ip: req.ip
    });
    throw createError('User not found', 404);
  }

  // Verify current password
  logger.info('🔐 Verifying current password', { userId: user.id });
  
  const isCurrentPasswordValid = await bcrypt.compare(currentPassword, user.password);
  if (!isCurrentPasswordValid) {
    logger.warn('❌ Password change failed - invalid current password', {
      userId: user.id,
      email: user.email,
      ip: req.ip
    });
    throw createError('Current password is incorrect', 400);
  }

  logger.info('✅ Current password verified', { userId: user.id });

  // Hash new password
  const saltRounds = 12;
  logger.info('🔐 Hashing new password', { userId: user.id, saltRounds });
  
  const hashedNewPassword = await bcrypt.hash(newPassword, saltRounds);

  // Update password
  logger.info('📝 Updating password in database', { userId: user.id });
  
  await prisma.user.update({
    where: { id: user.id },
    data: { password: hashedNewPassword },
  });

  // Invalidate all refresh tokens for this user
  logger.info('🗑️ Invalidating all refresh tokens for user', { userId: user.id });
  
  await prisma.refreshToken.deleteMany({
    where: { userId: user.id },
  });

  // Clear cookies to force re-login
  res.clearCookie('accessToken');
  res.clearCookie('refreshToken');

  logger.info('🍪 Authentication cookies cleared after password change', { userId: user.id });

  const responseTime = Date.now() - startTime;

  logger.info('🎉 Password changed successfully', {
    userId: user.id,
    email: user.email,
    role: user.role,
    responseTime: `${responseTime}ms`,
    ip: req.ip
  });

  res.json({
    success: true,
    message: 'Password changed successfully. Please log in again.',
  });
});

export const getProfile = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const startTime = Date.now();
  
  logger.info('👤 Profile retrieval attempt', {
    userId: req.user?.id,
    email: req.user?.email,
    role: req.user?.role,
    ip: req.ip
  });

  const user = await prisma.user.findUnique({
    where: { id: req.user!.id },
    select: {
      id: true,
      name: true,
      email: true,
      role: true,
      canEditBots: true,
      canCreateBots: true,
      profileImageUrl: true,
      emailVerified: true,
      createdAt: true,
      updatedAt: true,
    },
  });

  if (!user) {
    logger.error('❌ Profile retrieval failed - user not found', {
      userId: req.user?.id,
      ip: req.ip
    });
    throw createError('User not found', 404);
  }

  const responseTime = Date.now() - startTime;

  logger.info('✅ Profile retrieved successfully', {
    userId: user.id,
    email: user.email,
    role: user.role,
    responseTime: `${responseTime}ms`,
    ip: req.ip
  });

  res.json({
    success: true,
    data: {
      user: {
        ...user,
        canEditBots: user.role === 'TEACHER' ? (user.canEditBots ?? false) : undefined,
        canCreateBots: user.role === 'TEACHER' ? (user.canCreateBots ?? false) : undefined,
      },
    },
  });
});

export const updateProfile = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const startTime = Date.now();
  
  logger.info('✏️ Profile update attempt', {
    userId: req.user?.id,
    email: req.user?.email,
    role: req.user?.role,
    ip: req.ip,
    updateFields: Object.keys(req.body)
  });

  const validatedData: UpdateProfileInput = updateProfileSchema.parse(req.body);
  const { name, profileImageUrl } = validatedData;

  logger.info('✅ Profile update data validated', {
    userId: req.user?.id,
    nameLength: name?.length,
    hasProfileImage: !!profileImageUrl
  });

  const updateData: any = {};
  if (name !== undefined) updateData.name = name;
  if (profileImageUrl !== undefined) updateData.profileImageUrl = profileImageUrl;

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

  const responseTime = Date.now() - startTime;

  logger.info('🎉 Profile updated successfully', {
    userId: user.id,
    email: user.email,
    role: user.role,
    responseTime: `${responseTime}ms`,
    ip: req.ip
  });

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

