const express = require('express');
const { User, UserAddress } = require('../models'); // Import from models/index.js to get models with associations
const { authenticateToken, optionalAuth, requireAdmin } = require('../middleware/auth');
const logger = require('../utils/logger');
const userService = require('../services/UserService');
const { validateS3Keys } = require('../utils/s3KeyValidator');
const { IMAGE_LIMITS } = require('../config/imageLimits');
const router = express.Router();

// Allowed fields for profile update (prevents mass assignment)
const ALLOWED_PROFILE_FIELDS = [
  'firstName',
  'lastName',
  'email',
  'phone',
  'address1',
  'address2',
  'city',
  'state',
  'zipCode',
  'country',
  'imageFilename',
  'itemRequestNotificationRadius',
];

// Allowed fields for user address create/update (prevents mass assignment)
const ALLOWED_ADDRESS_FIELDS = [
  'address1',
  'address2',
  'city',
  'state',
  'zipCode',
  'country',
  'latitude',
  'longitude',
];

/**
 * Extract only allowed fields from request body
 */
function extractAllowedProfileFields(body) {
  const result = {};
  for (const field of ALLOWED_PROFILE_FIELDS) {
    if (body[field] !== undefined) {
      result[field] = body[field];
    }
  }
  return result;
}

/**
 * Extract only allowed address fields from request body
 */
function extractAllowedAddressFields(body) {
  const result = {};
  for (const field of ALLOWED_ADDRESS_FIELDS) {
    if (body[field] !== undefined) {
      result[field] = body[field];
    }
  }
  return result;
}

router.get('/profile', authenticateToken, async (req, res, next) => {
  try {
    const user = await User.findByPk(req.user.id, {
      attributes: { exclude: ['password'] }
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("User profile fetched", {
      userId: req.user.id
    });

    res.json(user);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("User profile fetch failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id
    });
    next(error);
  }
});

// Address routes (must come before /:id route)
router.get('/addresses', authenticateToken, async (req, res, next) => {
  try {
    const addresses = await UserAddress.findAll({
      where: { userId: req.user.id },
      order: [['isPrimary', 'DESC'], ['createdAt', 'ASC']]
    });
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("User addresses fetched", {
      userId: req.user.id,
      addressCount: addresses.length
    });

    res.json(addresses);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("User addresses fetch failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id
    });
    next(error);
  }
});

router.post('/addresses', authenticateToken, async (req, res, next) => {
  try {
    // Extract only allowed fields (prevents mass assignment)
    const allowedData = extractAllowedAddressFields(req.body);
    const address = await userService.createUserAddress(req.user.id, allowedData);

    res.status(201).json(address);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("User address creation failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id,
      addressData: logger.sanitize(req.body)
    });
    next(error);
  }
});

router.put('/addresses/:id', authenticateToken, async (req, res, next) => {
  try {
    // Extract only allowed fields (prevents mass assignment)
    const allowedData = extractAllowedAddressFields(req.body);
    const address = await userService.updateUserAddress(req.user.id, req.params.id, allowedData);

    res.json(address);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("User address update failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id,
      addressId: req.params.id
    });

    if (error.message === 'Address not found') {
      return res.status(404).json({ error: 'Address not found' });
    }

    next(error);
  }
});

router.delete('/addresses/:id', authenticateToken, async (req, res, next) => {
  try {
    await userService.deleteUserAddress(req.user.id, req.params.id);

    res.status(204).send();
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("User address deletion failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id,
      addressId: req.params.id
    });

    if (error.message === 'Address not found') {
      return res.status(404).json({ error: 'Address not found' });
    }

    next(error);
  }
});

// User availability routes (must come before /:id route)
router.get('/availability', authenticateToken, async (req, res, next) => {
  try {
    const user = await User.findByPk(req.user.id, {
      attributes: ['defaultAvailableAfter', 'defaultAvailableBefore', 'defaultSpecifyTimesPerDay', 'defaultWeeklyTimes']
    });
    res.json({
      generalAvailableAfter: user.defaultAvailableAfter,
      generalAvailableBefore: user.defaultAvailableBefore,
      specifyTimesPerDay: user.defaultSpecifyTimesPerDay,
      weeklyTimes: user.defaultWeeklyTimes
    });
  } catch (error) {
    next(error);
  }
});

router.put('/availability', authenticateToken, async (req, res, next) => {
  try {
    const { generalAvailableAfter, generalAvailableBefore, specifyTimesPerDay, weeklyTimes } = req.body;
    
    await User.update({
      defaultAvailableAfter: generalAvailableAfter,
      defaultAvailableBefore: generalAvailableBefore,
      defaultSpecifyTimesPerDay: specifyTimesPerDay,
      defaultWeeklyTimes: weeklyTimes
    }, {
      where: { id: req.user.id }
    });

    res.json({ message: 'Availability updated successfully' });
  } catch (error) {
    next(error);
  }
});

router.get('/:id', optionalAuth, async (req, res, next) => {
  try {
    const isAdmin = req.user?.role === 'admin';

    // Base attributes to exclude
    const excludedAttributes = ['password', 'email', 'phone', 'address', 'verificationToken', 'passwordResetToken'];

    // If not admin, also exclude ban-related fields
    if (!isAdmin) {
      excludedAttributes.push('isBanned', 'bannedAt', 'bannedBy', 'banReason');
    }

    const user = await User.findByPk(req.params.id, {
      attributes: { exclude: excludedAttributes }
    });

    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Public user profile fetched", {
      requestedUserId: req.params.id,
      viewerIsAdmin: isAdmin
    });

    res.json(user);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Public user profile fetch failed", {
      error: error.message,
      stack: error.stack,
      requestedUserId: req.params.id
    });
    next(error);
  }
});

router.put('/profile', authenticateToken, async (req, res, next) => {
  try {
    // Extract only allowed fields (prevents mass assignment)
    const allowedData = extractAllowedProfileFields(req.body);

    // Validate imageFilename if provided
    if (allowedData.imageFilename !== undefined && allowedData.imageFilename !== null) {
      const keyValidation = validateS3Keys([allowedData.imageFilename], 'profiles', { maxKeys: IMAGE_LIMITS.profile });
      if (!keyValidation.valid) {
        return res.status(400).json({
          error: keyValidation.error,
          details: keyValidation.invalidKeys
        });
      }
    }

    // Use UserService to handle update and email notification
    const updatedUser = await userService.updateProfile(req.user.id, allowedData);

    res.json(updatedUser);
  } catch (error) {
    logger.error('Profile update error', { error });
    next(error);
  }
});

// Admin: Ban a user
router.post('/admin/:id/ban', authenticateToken, requireAdmin, async (req, res, next) => {
  try {
    const { reason } = req.body;
    const targetUserId = req.params.id;

    // Validate reason is provided
    if (!reason || !reason.trim()) {
      return res.status(400).json({ error: "Ban reason is required" });
    }

    // Prevent banning yourself
    if (targetUserId === req.user.id) {
      return res.status(400).json({ error: "You cannot ban yourself" });
    }

    const targetUser = await User.findByPk(targetUserId);

    if (!targetUser) {
      return res.status(404).json({ error: "User not found" });
    }

    // Prevent banning other admins
    if (targetUser.role === 'admin') {
      return res.status(403).json({ error: "Cannot ban admin users" });
    }

    // Check if already banned
    if (targetUser.isBanned) {
      return res.status(400).json({ error: "User is already banned" });
    }

    // Ban the user (this also invalidates sessions via jwtVersion increment)
    await targetUser.banUser(req.user.id, reason.trim());

    // Send ban notification email
    try {
      const emailServices = require("../services/email");
      await emailServices.userEngagement.sendUserBannedNotification(
        targetUser,
        req.user,
        reason.trim()
      );
      const reqLogger = logger.withRequestId(req.id);
      reqLogger.info("User ban notification email sent", {
        bannedUserId: targetUserId,
        adminId: req.user.id
      });
    } catch (emailError) {
      // Log but don't fail the ban operation
      const reqLogger = logger.withRequestId(req.id);
      reqLogger.error('Failed to send user ban notification email', {
        error: emailError.message,
        stack: emailError.stack,
        bannedUserId: targetUserId,
        adminId: req.user.id
      });
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("User banned by admin", {
      targetUserId,
      adminId: req.user.id,
      reason: reason.trim()
    });

    // Return updated user data (excluding sensitive fields)
    const updatedUser = await User.findByPk(targetUserId, {
      attributes: { exclude: ['password', 'verificationToken', 'passwordResetToken'] }
    });

    res.json({
      message: "User has been banned successfully",
      user: updatedUser
    });
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Admin ban user failed", {
      error: error.message,
      stack: error.stack,
      targetUserId: req.params.id,
      adminId: req.user.id
    });
    next(error);
  }
});

// Admin: Unban a user
router.post('/admin/:id/unban', authenticateToken, requireAdmin, async (req, res, next) => {
  try {
    const targetUserId = req.params.id;

    const targetUser = await User.findByPk(targetUserId);

    if (!targetUser) {
      return res.status(404).json({ error: "User not found" });
    }

    // Check if user is actually banned
    if (!targetUser.isBanned) {
      return res.status(400).json({ error: "User is not banned" });
    }

    // Unban the user
    await targetUser.unbanUser();

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("User unbanned by admin", {
      targetUserId,
      adminId: req.user.id
    });

    // Return updated user data (excluding sensitive fields)
    const updatedUser = await User.findByPk(targetUserId, {
      attributes: { exclude: ['password', 'verificationToken', 'passwordResetToken'] }
    });

    res.json({
      message: "User has been unbanned successfully",
      user: updatedUser
    });
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Admin unban user failed", {
      error: error.message,
      stack: error.stack,
      targetUserId: req.params.id,
      adminId: req.user.id
    });
    next(error);
  }
});

module.exports = router;