const express = require('express');
const helmet = require('helmet');
const { Message, User } = require('../models');
const { authenticateToken } = require('../middleware/auth');
const logger = require('../utils/logger');
const { emitNewMessage, emitMessageRead } = require('../sockets/messageSocket');
const { Op } = require('sequelize');
const emailServices = require('../services/email');
const fs = require('fs');
const path = require('path');
const { validateS3Keys } = require('../utils/s3KeyValidator');
const { IMAGE_LIMITS } = require('../config/imageLimits');
const router = express.Router();

// Get all messages for the current user (inbox)
router.get('/', authenticateToken, async (req, res, next) => {
  try {
    const messages = await Message.findAll({
      where: { receiverId: req.user.id },
      include: [
        {
          model: User,
          as: 'sender',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        }
      ],
      order: [['createdAt', 'DESC']]
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Messages inbox fetched", {
      userId: req.user.id,
      messageCount: messages.length
    });

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

// Get conversations grouped by user pairs
router.get('/conversations', authenticateToken, async (req, res, next) => {
  try {
    const userId = req.user.id;

    // Fetch all messages where user is sender or receiver
    const allMessages = await Message.findAll({
      where: {
        [Op.or]: [
          { senderId: userId },
          { receiverId: userId }
        ]
      },
      include: [
        {
          model: User,
          as: 'sender',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        },
        {
          model: User,
          as: 'receiver',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        }
      ],
      order: [['createdAt', 'DESC']]
    });

    // Group messages by conversation partner
    const conversationsMap = new Map();

    allMessages.forEach(message => {
      // Determine the conversation partner
      const partnerId = message.senderId === userId ? message.receiverId : message.senderId;
      const partner = message.senderId === userId ? message.receiver : message.sender;

      if (!conversationsMap.has(partnerId)) {
        conversationsMap.set(partnerId, {
          partnerId,
          partner: partner ? {
            id: partner.id,
            firstName: partner.firstName,
            lastName: partner.lastName,
            profileImage: partner.profileImage
          } : null,
          lastMessage: null,
          lastMessageAt: null,
          unreadCount: 0
        });
      }

      const conversation = conversationsMap.get(partnerId);

      // Count unread messages (only those received by current user)
      if (message.receiverId === userId && !message.isRead) {
        conversation.unreadCount++;
      }

      // Keep the most recent message (messages are already sorted DESC)
      if (!conversation.lastMessage) {
        conversation.lastMessage = {
          id: message.id,
          content: message.content,
          senderId: message.senderId,
          createdAt: message.createdAt,
          isRead: message.isRead
        };
        conversation.lastMessageAt = message.createdAt;
      }
    });

    // Convert to array and sort by most recent message first
    const conversations = Array.from(conversationsMap.values())
      .filter(conv => conv.partner !== null) // Filter out conversations with deleted users
      .sort((a, b) => new Date(b.lastMessageAt) - new Date(a.lastMessageAt));

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Conversations fetched", {
      userId: req.user.id,
      conversationCount: conversations.length
    });

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

// Get sent messages
router.get('/sent', authenticateToken, async (req, res, next) => {
  try {
    const messages = await Message.findAll({
      where: { senderId: req.user.id },
      include: [
        {
          model: User,
          as: 'receiver',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        }
      ],
      order: [['createdAt', 'DESC']]
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Sent messages fetched", {
      userId: req.user.id,
      messageCount: messages.length
    });

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

// Get a single message
router.get('/:id', authenticateToken, async (req, res, next) => {
  try {
    const message = await Message.findOne({
      where: {
        id: req.params.id,
        [require('sequelize').Op.or]: [
          { senderId: req.user.id },
          { receiverId: req.user.id }
        ]
      },
      include: [
        {
          model: User,
          as: 'sender',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        },
        {
          model: User,
          as: 'receiver',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        }
      ]
    });

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

    // Mark as read if user is the receiver
    const wasUnread = message.receiverId === req.user.id && !message.isRead;
    if (wasUnread) {
      await message.update({ isRead: true });

      // Emit socket event to sender for real-time read receipt
      const io = req.app.get('io');
      if (io) {
        emitMessageRead(io, message.senderId, {
          messageId: message.id,
          readAt: new Date().toISOString(),
          readBy: req.user.id
        });
      }
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Message fetched", {
      userId: req.user.id,
      messageId: req.params.id,
      markedAsRead: wasUnread
    });

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

// Send a new message
router.post('/', authenticateToken, async (req, res, next) => {
  try {
    const { receiverId, content, imageFilename } = req.body;

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

    // Check if receiver exists
    const receiver = await User.findByPk(receiverId);
    if (!receiver) {
      return res.status(404).json({ error: 'Receiver not found' });
    }

    // Prevent sending messages to self
    if (receiverId === req.user.id) {
      return res.status(400).json({ error: 'Cannot send messages to yourself' });
    }

    const message = await Message.create({
      senderId: req.user.id,
      receiverId,
      content,
      imageFilename: imageFilename || null
    });

    const messageWithSender = await Message.findByPk(message.id, {
      include: [{
        model: User,
        as: 'sender',
        attributes: ['id', 'firstName', 'lastName', 'imageFilename']
      }]
    });

    // Emit socket event to receiver for real-time notification
    const io = req.app.get('io');
    if (io) {
      emitNewMessage(io, receiverId, messageWithSender.toJSON());
    }

    // Send email notification to receiver
    try {
      const sender = await User.findByPk(req.user.id, {
        attributes: ['id', 'firstName', 'lastName', 'email']
      });

      await emailServices.messaging.sendNewMessageNotification(receiver, sender, message);
    } catch (emailError) {
      // Log email error but don't block the message send
      const reqLogger = logger.withRequestId(req.id);
      reqLogger.error("Failed to send message notification email", {
        error: emailError.message,
        stack: emailError.stack,
        messageId: message.id,
        receiverId: receiverId
      });
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Message sent", {
      senderId: req.user.id,
      receiverId: receiverId,
      messageId: message.id
    });

    res.status(201).json(messageWithSender);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Message send failed", {
      error: error.message,
      stack: error.stack,
      senderId: req.user.id,
      receiverId: req.body?.receiverId
    });
    next(error);
  }
});

// Mark message as read
router.put('/:id/read', authenticateToken, async (req, res, next) => {
  try {
    const message = await Message.findOne({
      where: { 
        id: req.params.id,
        receiverId: req.user.id
      }
    });

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

    await message.update({ isRead: true });

    // Emit socket event to sender for real-time read receipt
    const io = req.app.get('io');
    if (io) {
      emitMessageRead(io, message.senderId, {
        messageId: message.id,
        readAt: new Date().toISOString(),
        readBy: req.user.id
      });
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Message marked as read", {
      userId: req.user.id,
      messageId: req.params.id
    });

    res.json(message);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Message mark as read failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id,
      messageId: req.params.id
    });
    next(error);
  }
});

// Get unread message count
router.get('/unread/count', authenticateToken, async (req, res, next) => {
  try {
    const count = await Message.count({
      where: { 
        receiverId: req.user.id,
        isRead: false
      }
    });
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Unread message count fetched", {
      userId: req.user.id,
      unreadCount: count
    });

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

// Get message image (authorized)
router.get('/images/:filename',
  authenticateToken,
  // Override Helmet's CORP header for cross-origin image loading
  helmet.crossOriginResourcePolicy({ policy: "cross-origin" }),
  async (req, res) => {
  try {
    // Sanitize filename to prevent path traversal attacks
    const filename = path.basename(req.params.filename);

    // Verify user is sender or receiver of a message with this image
    const message = await Message.findOne({
      where: {
        imageFilename: filename,
        [Op.or]: [
          { senderId: req.user.id },
          { receiverId: req.user.id }
        ]
      }
    });

    if (!message) {
      const reqLogger = logger.withRequestId(req.id);
      reqLogger.warn('Unauthorized image access attempt', {
        userId: req.user.id,
        filename
      });
      return res.status(403).json({ error: 'Access denied' });
    }

    // Serve the image
    const filePath = path.join(__dirname, '../uploads/messages', filename);

    if (!fs.existsSync(filePath)) {
      return res.status(404).json({ error: 'Image not found' });
    }

    res.sendFile(filePath);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error('Image serve failed', {
      error: error.message,
      stack: error.stack,
      filename: req.params.filename
    });
    res.status(500).json({ error: 'Failed to load image' });
  }
});

module.exports = router;