const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const session = require('express-session');
const path = require('path');
const cors = require('cors');

// Import configuration
const config = require('./config/config');

const Database = require('./database/database');
const authRoutes = require('./routes/auth');
const chatRoutes = require('./routes/chat');

// Import middleware
const { Logger, errorHandler, notFoundHandler, asyncHandler, socketErrorHandler, ValidationError, handleDatabaseError } = require('./middleware/errorHandler');
const { Validator, createRateLimit, checkSocketRateLimit } = require('./middleware/validation');

const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
  cors: {
    origin: config.security.corsOrigin,
    methods: ["GET", "POST"]
  }
});

// Initialize database
const db = new Database();
db.init();

// Middleware
app.use(cors({
  origin: config.security.corsOrigin,
  credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/uploads', express.static(config.upload.uploadDir));

// Rate limiting
app.use('/api/', createRateLimit(config.rateLimit.api.windowMs, config.rateLimit.api.maxRequests));
app.use('/auth/', createRateLimit(config.rateLimit.auth.windowMs, config.rateLimit.auth.maxRequests));

// Session configuration
app.use(session({
  secret: config.session.secret,
  resave: false,
  saveUninitialized: false,
  cookie: { 
    secure: config.session.secure,
    maxAge: config.session.maxAge
  }
}));

// Passport configuration
app.use(passport.initialize());
app.use(passport.session());

// Google OAuth Strategy
passport.use(new GoogleStrategy({
  clientID: config.google.clientId,
  clientSecret: config.google.clientSecret,
  callbackURL: config.google.callbackURL
}, async (accessToken, refreshToken, profile, done) => {
  try {
    // Check if user exists in database
    let user = await db.getUserByGoogleId(profile.id);
    
    if (!user) {
      // Create new user
      user = await db.createUser({
        googleId: profile.id,
        name: profile.displayName,
        email: profile.emails[0].value,
        avatar: profile.photos[0].value
      });
    }
    
    return done(null, user);
  } catch (error) {
    return done(error, null);
  }
}));

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
  try {
    const user = await db.getUserById(id);
    done(null, user);
  } catch (error) {
    done(error, null);
  }
});

// Routes
app.use('/auth', authRoutes);
app.use('/api/chat', chatRoutes);

// Serve main page
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// Socket.IO connection handling
const connectedUsers = new Map();

io.on('connection', (socket) => {
  Logger.info(`User connected: ${socket.id}`);

  // Handle user authentication
  socket.on('authenticate', async (userId) => {
    try {
      // Validate user ID
      const validUserId = Validator.validateUserId(userId);
      
      const user = await db.getUserById(validUserId);
      if (user) {
        connectedUsers.set(socket.id, user);
        socket.userId = validUserId;
        
        Logger.info(`User authenticated: ${user.name} (${user.id})`);
        
        // Broadcast user online status
        socket.broadcast.emit('user_online', {
          userId: user.id,
          name: user.name,
          avatar: user.avatar
        });
        
        // Send online users list
        const onlineUsers = Array.from(connectedUsers.values());
        socket.emit('online_users', onlineUsers);
      } else {
        socketErrorHandler(socket, new Error('User not found'), 'authentication');
      }
    } catch (error) {
      socketErrorHandler(socket, error, 'authentication');
    }
  });

  // Handle new message
  socket.on('send_message', async (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) {
        socketErrorHandler(socket, new Error('User not authenticated'), 'send_message');
        return;
      }

      // Rate limiting
      if (!checkSocketRateLimit(socket.id, 'send_message', config.rateLimit.socket.message.maxRequests, config.rateLimit.socket.message.windowMs)) {
        socketErrorHandler(socket, new Error('Rate limit exceeded'), 'send_message');
        return;
      }

      // Validate message content
      const validContent = Validator.validateMessage(data.message);

      const message = await db.saveMessage({
        userId: user.id,
        content: validContent,
        timestamp: new Date().toISOString(),
        messageType: data.messageType || 'text'
      });

      const messageWithUser = {
        id: message.id,
        content: message.content,
        timestamp: message.timestamp,
        messageType: message.message_type,
        files: data.files || [],
        user: {
          id: user.id,
          name: user.name,
          avatar: user.avatar
        }
      };

      Logger.info(`Message sent by ${user.name}: ${validContent.substring(0, 50)}...`);
      
      // Broadcast message to all connected clients
      io.emit('new_message', messageWithUser);
    } catch (error) {
      socketErrorHandler(socket, error, 'send_message');
    }
  });

  // Handle file upload notification
  socket.on('file_uploaded', (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) {
        socketErrorHandler(socket, new Error('User not authenticated'), 'file_uploaded');
        return;
      }

      Logger.info(`File uploaded by ${user.name}`);
      
      // Broadcast file message to all connected clients
      io.emit('new_message', data);
    } catch (error) {
      socketErrorHandler(socket, error, 'file_uploaded');
    }
  });

  // Handle typing indicator
  socket.on('typing', (data) => {
    const user = connectedUsers.get(socket.id);
    if (user) {
      socket.broadcast.emit('user_typing', {
        userId: user.id,
        name: user.name,
        isTyping: data.isTyping
      });
    }
  });

  // Handle typing indicator
  socket.on('typing_start', (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) {
        socketErrorHandler(socket, new Error('User not authenticated'), 'typing_start');
        return;
      }

      // Rate limiting for typing events
      if (!checkSocketRateLimit(socket.id, 'typing', config.rateLimit.socket.typing.maxRequests, config.rateLimit.socket.typing.windowMs)) {
        return; // Silently ignore excessive typing events
      }

      const { privateChatId, otherUserId } = data;
      
      // Validate input
      const validOtherUserId = Validator.validateUserId(otherUserId);
      const validChatId = Validator.validatePrivateChatId(privateChatId);
      
      // Find the other user's socket
      const otherUserSocket = Array.from(connectedUsers.entries())
        .find(([socketId, userData]) => userData.id == validOtherUserId);
      
      if (otherUserSocket) {
        const [otherSocketId] = otherUserSocket;
        io.to(otherSocketId).emit('user_typing', {
          privateChatId: validChatId,
          userId: user.id,
          userName: user.name
        });
      }
    } catch (error) {
      socketErrorHandler(socket, error, 'typing_start');
    }
  });

  socket.on('typing_stop', (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) {
        socketErrorHandler(socket, new Error('User not authenticated'), 'typing_stop');
        return;
      }

      const { privateChatId, otherUserId } = data;
      
      // Validate input
      const validOtherUserId = Validator.validateUserId(otherUserId);
      const validChatId = Validator.validatePrivateChatId(privateChatId);
      
      // Find the other user's socket
      const otherUserSocket = Array.from(connectedUsers.entries())
        .find(([socketId, userData]) => userData.id == validOtherUserId);
      
      if (otherUserSocket) {
        const [otherSocketId] = otherUserSocket;
        io.to(otherSocketId).emit('user_stop_typing', {
          privateChatId: validChatId,
          userId: user.id
        });
      }
    } catch (error) {
      socketErrorHandler(socket, error, 'typing_stop');
    }
  });

  // Handle mark messages as read
  socket.on('mark_messages_read', async (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) {
        socketErrorHandler(socket, new Error('User not authenticated'), 'mark_messages_read');
        return;
      }

      const { privateChatId, otherUserId } = data;
      
      // Validate input
      const validOtherUserId = Validator.validateUserId(otherUserId);
      const validChatId = Validator.validatePrivateChatId(privateChatId);
      
      // Mark messages as read in database
      await db.markMessagesAsRead(validChatId, user.id);
      
      Logger.info(`Messages marked as read by ${user.name} in chat ${validChatId}`);
      
      // Notify the other user that messages have been read
      const otherUserSocket = Array.from(connectedUsers.entries())
        .find(([socketId, userData]) => userData.id == validOtherUserId);
      
      if (otherUserSocket) {
        const [otherSocketId] = otherUserSocket;
        io.to(otherSocketId).emit('messages_read', {
          privateChatId: validChatId,
          readBy: user.id
        });
      }
    } catch (error) {
      socketErrorHandler(socket, error, 'mark_messages_read');
    }
  });

  // Handle private message
  socket.on('send_private_message', async (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) {
        socketErrorHandler(socket, new Error('User not authenticated'), 'send_private_message');
        return;
      }

      // Rate limiting
      if (!checkSocketRateLimit(socket.id, 'send_private_message', config.rateLimit.socket.privateMessage.maxRequests, config.rateLimit.socket.privateMessage.windowMs)) {
        socketErrorHandler(socket, new Error('Rate limit exceeded'), 'send_private_message');
        return;
      }

      const { privateChatId, otherUserId, message, messageType = 'text' } = data;
      
      // Validate input
      const validOtherUserId = Validator.validateUserId(otherUserId);
      const validMessage = Validator.validateMessage(message);
      const validChatId = Validator.validatePrivateChatId(privateChatId);
      
      // Save message to database
      const savedMessage = await db.saveMessage({
        userId: user.id,
        content: validMessage,
        timestamp: new Date().toISOString(),
        messageType: messageType,
        privateChatId: validChatId
      });

      const messageWithUser = {
        id: savedMessage.id,
        content: savedMessage.content,
        timestamp: savedMessage.timestamp,
        messageType: savedMessage.message_type,
        privateChatId: savedMessage.private_chat_id,
        status: 'sent',
        user: {
          id: user.id,
          name: user.name,
          avatar: user.avatar
        }
      };

      Logger.info(`Private message sent from ${user.name} to user ${validOtherUserId}`);
      
      // Find the other user's socket
      const otherUserSocket = Array.from(connectedUsers.entries())
        .find(([socketId, userData]) => userData.id == validOtherUserId);
      
      if (otherUserSocket) {
        const [otherSocketId] = otherUserSocket;
        // Send message to the other user
        io.to(otherSocketId).emit('new_private_message', messageWithUser);
        
        // Update message status to delivered
        await db.updateMessageStatus(savedMessage.id, 'delivered');
        messageWithUser.status = 'delivered';
      }
      
      
      // Send confirmation back to sender
      socket.emit('private_message_sent', messageWithUser);
    } catch (error) {
      socketErrorHandler(socket, error, 'send_private_message');
    }
  });

  // Handle private file upload notification
  socket.on('private_file_uploaded', (data) => {
    try {
      const user = connectedUsers.get(socket.id);
      if (!user) return;

      const { otherUserId, message } = data;
      
      // Find the other user's socket
      const otherUserSocket = Array.from(connectedUsers.entries())
        .find(([socketId, userData]) => userData.id == otherUserId);
      
      if (otherUserSocket) {
        const [otherSocketId] = otherUserSocket;
        // Send file message to the other user
        io.to(otherSocketId).emit('new_private_message', message);
      }
      
      // Send confirmation back to sender
      socket.emit('private_message_sent', message);
    } catch (error) {
      console.error('Private file upload error:', error);
    }
  });

  // Handle private typing indicator
  socket.on('private_typing', (data) => {
    const user = connectedUsers.get(socket.id);
    if (user) {
      const { otherUserId, isTyping } = data;
      
      // Find the other user's socket
      const otherUserSocket = Array.from(connectedUsers.entries())
        .find(([socketId, userData]) => userData.id == otherUserId);
      
      if (otherUserSocket) {
        const [otherSocketId] = otherUserSocket;
        io.to(otherSocketId).emit('private_user_typing', {
          userId: user.id,
          name: user.name,
          isTyping
        });
      }
    }
  });

  // Handle joining private chat room
  socket.on('join_private_chat', (privateChatId) => {
    socket.join(`private_${privateChatId}`);
  });

  // Handle leaving private chat room
  socket.on('leave_private_chat', (privateChatId) => {
    socket.leave(`private_${privateChatId}`);
  });

  // Handle disconnect
  socket.on('disconnect', () => {
    const user = connectedUsers.get(socket.id);
    if (user) {
      connectedUsers.delete(socket.id);
      
      // Broadcast user offline status
      socket.broadcast.emit('user_offline', {
        userId: user.id,
        name: user.name
      });
    }
    Logger.info(`User disconnected: ${socket.id}`);
  });
});

// Error handling middleware (must be last)
app.use(notFoundHandler);
app.use(errorHandler);

// Global error handlers
process.on('uncaughtException', (error) => {
  Logger.error('Uncaught Exception', error);
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  Logger.error('Unhandled Rejection at Promise', reason);
  process.exit(1);
});

const PORT = config.server.port;
server.listen(PORT, config.server.host, () => {
  Logger.info(`Server running on ${config.server.host}:${PORT} in ${config.server.nodeEnv} mode`);
  console.log(`Visit http://${config.server.host}:${PORT} to access TChat`);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  Logger.info('SIGTERM received, shutting down gracefully');
  server.close(() => {
    Logger.info('Process terminated');
    process.exit(0);
  });
});

process.on('SIGINT', () => {
  Logger.info('SIGINT received, shutting down gracefully');
  server.close(() => {
    Logger.info('Process terminated');
    process.exit(0);
  });
});