const dotenv = require('dotenv');
// Загружаем переменные окружения как можно раньше
dotenv.config();

const express = require('express');
const http = require('http'); // <--- Импорт http
const { Server } = require("socket.io"); // <--- Импорт Server из socket.io
const conversationRoutes = require('./routes/conversationRoutes');
// const dotenv = require('dotenv'); // Уже импортировано выше
const cors = require('cors');
// Импортируем модуль для подключения к БД
const connectDBModule = require('./config/db');
const authRoutes = require('./routes/authRoutes'); // <--- 1. РАСКОММЕНТИРУЙ ИЛИ ДОБАВЬ ЭТОТ ИМПОРТ
const userRoutes = require('./routes/userRoutes'); // 1. Импортируем userRoutes
const actionRoutes = require('./routes/actionRoutes'); // 1. Импортируем actionRoutes
const adminRoutes = require('./routes/adminRoutes'); // Импортируем маршруты админа
const reportRoutes = require('./routes/reportRoutes'); // Импортируем маршруты для жалоб
const securityRoutes = require('./routes/securityRoutes'); // Импортируем маршруты безопасности
const Message = require('./models/Message'); // Импорт модели Message
const Conversation = require('./models/Conversation'); // Импорт модели Conversation
const initAdminAccount = require('./utils/initAdmin'); // Импорт инициализации админ-аккаунта

// Загружаем переменные окружения до их использования
// dotenv.config(); // Перемещено в начало файла

// Проверяем, что JWT_SECRET правильно загружен
console.log('JWT_SECRET loaded:', process.env.JWT_SECRET ? 'Yes' : 'No');

// Добавляем отладочное логирование
console.log('Тип импортированного модуля connectDB:', typeof connectDBModule);

const app = express();
const server = http.createServer(app); // <--- Создание HTTP сервера
const io = new Server(server, { // <--- Инициализация socket.io
  cors: {
    origin: "*", // <--- Разрешаем все источники
    methods: ["GET", "POST"]
  }
});

// Сделать экземпляр io доступным в запросах
app.set('io', io);

// Подключение к базе данных - проверяем, что импортировали функцию
if (typeof connectDBModule === 'function') {  try {
    connectDBModule() // Вызов функции подключения
      .then(async () => {
        console.log('Успешное подключение к базе данных');
        console.log('Инициализация административного аккаунта...');
        // Инициализируем админ-аккаунт после подключения к БД
        try {
          await initAdminAccount();
          console.log('Инициализация администратора завершена');
        } catch (adminError) {
          console.error('Ошибка при инициализации административного аккаунта:', adminError);
        }
      })
      .catch(err => {
        console.error('Ошибка при подключении к базе данных:', err);
      });
    console.log('Попытка подключения к базе данных была инициирована...'); // Изменил лог для ясности
  } catch (err) {
    // Этот catch здесь, скорее всего, не поймает асинхронные ошибки из connectDBModule,
    // так как connectDBModule асинхронная и не возвращает Promise, который здесь ожидался бы с await.
    // Ошибки из connectDBModule (если они есть) будут выведены внутри самой функции connectDBModule.
    console.error('Синхронная ошибка при вызове connectDBModule (маловероятно):', err);
  }
} else {
  console.error('Ошибка: connectDB не является функцией! Тип:', typeof connectDBModule);
}

// Настройка CORS для Express
app.use(cors({
  origin: "*", // <--- Разрешаем все источники
  credentials: true // Если нужны куки или заголовки авторизации
}));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get('/', (req, res) => {
  res.send('API для Dating App работает!');
});

// Подключаем маршруты
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes); // 2. Подключаем userRoutes с префиксом /api/users
app.use('/api/actions', actionRoutes);
app.use('/api/conversations', conversationRoutes);
app.use('/api/reports', reportRoutes); // Подключаем маршруты для жалоб
app.use('/api/admin', adminRoutes); // Подключаем маршруты для админ-панели
app.use('/api/security', securityRoutes); // Подключаем маршруты для системы безопасности устройств

// Socket.IO логика
let activeUsers = [];

// Делаем массив activeUsers доступным для контроллеров
app.set('activeUsers', activeUsers);

io.on("connection", (socket) => {
  console.log("A user connected:", socket.id);

  // Добавление нового пользователя
  socket.on("addUser", (userId) => {
    // Проверяем, не добавлен ли уже пользователь с таким userId, но другим socket.id
    // Это может произойти, если пользователь открыл несколько вкладок или переподключился
    const existingUser = activeUsers.find(user => user.userId === userId);
    if (existingUser) {
      // Обновляем socket.id для существующего userId
      existingUser.socketId = socket.id;
    } else {
      // Добавляем нового пользователя
      activeUsers.push({ userId, socketId: socket.id });
    }

    // Обновляем массив пользователей в приложении для доступа из контроллеров
    app.set('activeUsers', activeUsers);
    
    io.emit("getUsers", activeUsers); // Отправляем обновленный список активных пользователей всем клиентам
    console.log("User added to active users:", userId, socket.id);
    console.log("Active users:", activeUsers);
  });

  // Вспомогательная функция для получения сокета пользователя
  const getUserSocket = (userId) => {
    return activeUsers.find(u => u.userId === userId);
  };

  socket.on("joinConversationRoom", (roomId) => {
    socket.join(roomId);
    console.log(`[Socket.IO] User ${socket.id} joined room ${roomId}`);
  });

  socket.on("leaveConversationRoom", (roomId) => {
    socket.leave(roomId);
    console.log(`[Socket.IO] User ${socket.id} left room ${roomId}`);
  });

  // Отправка сообщения
  socket.on("sendMessage", async ({ senderId, receiverId, text, clientConversationId }) => { // Добавлен clientConversationId, async
    console.log(`"[Socket.IO] Получено сообщение от ${senderId} для ${receiverId}: "${text}" (попытка для диалога: ${clientConversationId})"`);

    const getUserSocket = (userId) => {
      const user = activeUsers.find(u => u.userId === userId);
      return user ? user : null; // Возвращаем объект пользователя или null
    };

    try {
      // 1. Найти или создать диалог
      let conversation = await Conversation.findOne({
        participants: { $all: [senderId, receiverId] }
      });

      if (!conversation) {
        console.log(`[Socket.IO] Диалог не найден, создаем новый между ${senderId} и ${receiverId}`);
        conversation = new Conversation({
          participants: [senderId, receiverId]
          // lastMessage будет установлено позже
        });
        // Не сохраняем conversation здесь сразу, сохраним вместе с lastMessage
      } else {
        console.log(`[Socket.IO] Найден существующий диалог: ${conversation._id}`);
      }
      
      // 2. Создать и сохранить новое сообщение
      const newMessage = new Message({
        conversationId: conversation._id, // Используем ID найденного или нового (но еще не сохраненного) диалога
        sender: senderId,
        text: text,
        status: 'sending' // Начальный статус - отправка
      });
      await newMessage.save();
      console.log(`[Socket.IO] Сообщение сохранено в БД: ${newMessage._id}`);

      // 3. Обновить lastMessage и updatedAt в диалоге
      conversation.lastMessage = newMessage._id;
      // conversation.lastMessageTimestamp = newMessage.createdAt; // Если используем это поле
      await conversation.save(); // Теперь сохраняем диалог (или обновляем существующий)
      console.log(`[Socket.IO] Диалог ${conversation._id} обновлен с lastMessage.`);

      // 4. Подготовить данные сообщения для отправки клиентам (с populated sender)
      const populatedMessage = await Message.findById(newMessage._id)
                                     .populate('sender', 'name photos'); // Загружаем данные отправителя

      // 5. Отправить сообщение обоим участникам диалога (отправителю для подтверждения и получателю)
      const senderSocketInfo = getUserSocket(senderId); 
      const receiverSocketInfo = getUserSocket(receiverId);

      const messageToSend = {
          _id: populatedMessage._id,
          conversationId: populatedMessage.conversationId,
          sender: populatedMessage.sender, // Объект с name, photos
          text: populatedMessage.text,
          createdAt: populatedMessage.createdAt,
          status: populatedMessage.status
      };

      // После успешного сохранения в БД меняем статус на "delivered"
      await Message.findByIdAndUpdate(newMessage._id, { status: 'delivered' });
      messageToSend.status = 'delivered';  // Убедимся, что статус установлен явно

      if (senderSocketInfo && senderSocketInfo.socketId) {
        io.to(senderSocketInfo.socketId).emit("getMessage", messageToSend);
        console.log(`[Socket.IO] Сообщение (подтверждение) отправлено отправителю ${senderId}`);
      }

      if (receiverSocketInfo && receiverSocketInfo.socketId) {
        io.to(receiverSocketInfo.socketId).emit("getMessage", messageToSend);
        console.log(`[Socket.IO] Сообщение отправлено получателю ${receiverId}`);
      } else {
        console.log(`[Socket.IO] Получатель ${receiverId} не онлайн / сокет не найден.`);
        // TODO: Push-уведомление
      }

    } catch (error) {
      console.error('[Socket.IO] Ошибка при обработке sendMessage:', error.message, error.stack);
      const senderSocketInfo = getUserSocket(senderId);
      if (senderSocketInfo && senderSocketInfo.socketId) {
        io.to(senderSocketInfo.socketId).emit("messageError", { 
            message: "Не удалось отправить сообщение.", 
            originalText: text 
        });
      }
    }
  });

  // Событие: Пользователь прочитал сообщения
  socket.on("markMessagesAsRead", async ({ conversationId, userId }) => {
    console.log(`[Socket.IO] User ${userId} marked messages as read in conversation ${conversationId}`);
    try {
      // 1. Найти все сообщения в диалоге, отправленные НЕ userId, и где userId еще нет в readBy
      const result = await Message.updateMany(
        {
          conversationId: conversationId,
          sender: { $ne: userId }, // Сообщения от другого пользователя
          readBy: { $ne: userId }    // Которые текущий пользователь еще не читал (его ID нет в readBy)
        },
        {
          $addToSet: { readBy: userId }, // Добавить ID текущего пользователя в массив readBy
          $set: { status: 'read' }      // Обновляем статус сообщения на "прочитано"
        }
      );

      console.log(`[Socket.IO] Messages updated for read status: ${result.modifiedCount} in conversation ${conversationId} by user ${userId}`);

      if (result.modifiedCount > 0) {
        // 2. Найти другого участника диалога, чтобы уведомить его
        const conversation = await Conversation.findById(conversationId);
        if (!conversation) {
          console.log(`[Socket.IO] Conversation ${conversationId} not found for sending read notification.`);
          return;
        }

        const otherParticipantId = conversation.participants.find(pId => pId.toString() !== userId);
        
        if (otherParticipantId) {
          const otherUserSocket = getUserSocket(otherParticipantId.toString());
          if (otherUserSocket && otherUserSocket.socketId) {
            io.to(otherUserSocket.socketId).emit("messagesRead", {
              conversationId: conversationId,
              readerId: userId, // ID пользователя, который прочитал сообщения
              status: 'read'   // Добавляем статус "прочитано"
            });
            console.log(`[Socket.IO] Sent 'messagesRead' event to ${otherParticipantId} for conversation ${conversationId}`);
          } else {
            console.log(`[Socket.IO] Other participant ${otherParticipantId} for messagesRead notification is not online or socket not found.`);
          }
        }
      }
    } catch (error) {
      console.error('[Socket.IO] Error in markMessagesAsRead:', error.message, error.stack);
      // Можно отправить ошибку обратно клиенту, если это необходимо
      // socket.emit('markMessagesAsReadError', { conversationId, message: 'Failed to mark messages as read' });
    }
  });

  // Пользователь печатает
  socket.on("typing", ({ conversationId, receiverId, senderId }) => { // Добавлен senderId, conversationId
    const receiver = activeUsers.find(user => user.userId === receiverId);
    if (receiver) {
      io.to(receiver.socketId).emit("userTyping", { conversationId, senderId }); // Добавлен conversationId
    }
  });

  // Пользователь перестал печатать
  socket.on("stopTyping", ({ conversationId, receiverId, senderId }) => { // Добавлен senderId, conversationId
    const receiver = activeUsers.find(user => user.userId === receiverId);
    if (receiver) {
      io.to(receiver.socketId).emit("userStopTyping", { conversationId, senderId }); // Добавлен conversationId
    }
  });
  
  // Отключение пользователя
  socket.on("disconnect", () => {
    activeUsers = activeUsers.filter(user => user.socketId !== socket.id);
    io.emit("getUsers", activeUsers); // Обновляем список у всех клиентов
    console.log("User disconnected:", socket.id);
    console.log("Active users:", activeUsers);
  });
});


const errorHandler = (err, req, res, next) => {
  console.error('--- ERROR HANDLER ---');
  console.error('Name:', err.name);
  console.error('Message:', err.message);
  console.error('StatusCode on err object:', err.statusCode);
  console.error('Current res.statusCode:', res.statusCode);
  // console.error('Stack:', err.stack); 

  let finalStatusCode;
  let finalMessage = err.message || 'Произошла ошибка на сервере';

  // Приоритетная обработка специфических ошибок Mongoose
  if (err.name === 'CastError') {
    finalStatusCode = 400; // Или 404, если считаешь, что неверный ID = "не найдено"
    finalMessage = `Неверный формат ID ресурса: ${err.path} со значением '${err.value}' не является валидным ObjectId.`;
    console.log(`Mongoose CastError detected. Setting status to ${finalStatusCode}`);
  } else if (err.name === 'ValidationError') {
    finalStatusCode = 400;
    console.log(`Mongoose ValidationError detected. Setting status to ${finalStatusCode}`);
  } else if (err.statusCode) {
    finalStatusCode = err.statusCode;
    console.log(`Error has statusCode property. Setting status to ${finalStatusCode}`);
  } else {
    finalStatusCode = 500; // Default to 500
    console.log(`Defaulting status to ${finalStatusCode}`);
  }
  
  console.log(`[ERROR_HANDLER] Final StatusCode to be sent: ${finalStatusCode}`);

  if (res.headersSent) {
    console.error('[ERROR_HANDLER] Headers already sent.');
    return; 
  }

  res.status(finalStatusCode).json({
    message: finalMessage,
    stack: process.env.NODE_ENV === 'production' ? null : err.stack,
  });
};
app.use(errorHandler);

const PORT = process.env.PORT || 5000;

// Запуск HTTP сервера вместо app.listen
server.listen(PORT, () => {
  console.log(`Сервер запущен на порту ${PORT} и слушает WebSocket соединения`);
});