const jwt = require("jsonwebtoken");
const { User } = require("../models");
const logger = require("../utils/logger");
const cookie = require("cookie");

/**
 * Socket.io authentication middleware
 * Verifies JWT token and attaches user to socket
 * Tokens can be provided via:
 * 1. Cookie (accessToken) - preferred for browser clients
 * 2. Query parameter (token) - fallback for mobile/other clients
 */
const authenticateSocket = async (socket, next) => {
  try {
    let token = null;

    // Try to get token from cookies first (browser clients)
    if (socket.handshake.headers.cookie) {
      const cookies = cookie.parse(socket.handshake.headers.cookie);
      token = cookies.accessToken;
    }

    // Fallback to query parameter (mobile/other clients)
    if (!token && socket.handshake.auth?.token) {
      token = socket.handshake.auth.token;
    }

    // Fallback to legacy query parameter
    if (!token && socket.handshake.query?.token) {
      token = socket.handshake.query.token;
    }

    if (!token) {
      logger.warn("Socket connection rejected - no token provided", {
        socketId: socket.id,
        address: socket.handshake.address,
      });
      return next(new Error("Authentication required"));
    }

    // Verify JWT (access tokens only)
    const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
    const userId = decoded.id;

    if (!userId) {
      logger.warn("Socket connection rejected - invalid token format", {
        socketId: socket.id,
      });
      return next(new Error("Invalid token format"));
    }

    // Look up user
    const user = await User.findByPk(userId);

    if (!user) {
      logger.warn("Socket connection rejected - user not found", {
        socketId: socket.id,
        userId,
      });
      return next(new Error("User not found"));
    }

    // Validate JWT version (invalidate old tokens after password change)
    if (decoded.jwtVersion !== user.jwtVersion) {
      logger.warn("Socket connection rejected - JWT version mismatch", {
        socketId: socket.id,
        userId,
        tokenVersion: decoded.jwtVersion,
        userVersion: user.jwtVersion,
      });
      return next(
        new Error("Session expired due to password change. Please log in again.")
      );
    }

    // Attach user to socket for use in event handlers
    socket.userId = user.id;
    socket.user = {
      id: user.id,
      email: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
    };

    logger.info("Socket authenticated successfully", {
      socketId: socket.id,
      userId: user.id,
      email: user.email,
    });

    next();
  } catch (error) {
    // Check if token is expired
    if (error.name === "TokenExpiredError") {
      logger.warn("Socket connection rejected - token expired", {
        socketId: socket.id,
      });
      return next(new Error("Token expired"));
    }

    logger.error("Socket authentication error", {
      socketId: socket.id,
      error: error.message,
      stack: error.stack,
    });

    return next(new Error("Authentication failed"));
  }
};

module.exports = { authenticateSocket };
