const express = require('express');
const app = express();
require('dotenv').config(); // add dotnv for config
const server = require('http').createServer(app);
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const User = require('./db/models/User');
const Message = require('./db/models/Message');
const jwt = require('jsonwebtoken');


const PORT = process.env.PORT || 4000;
const TOKEN_KEY = process.env.TOKEN_KEY || 'rGH452fdfhdfg06hgj7'; 

const socket = require('socket.io');
const cors = require('cors');

app.get('/', (req, res) => {
  res.send('Hello from express server!')
})


app.use(cors());
app.use(express.json());

const io = socket(server, {
  cors: {
      origin: "http://localhost:3000"  //client endpoint and port
  }
});

const generateToken = (id, userName, isAdmin) => {
  const payload = {
      id,
      userName,
      isAdmin
  }
  return jwt.sign(payload, TOKEN_KEY);
}

const getOneUser = async (userName) => {
  const userInDb = await User.findOne({userName});
  return userInDb;
}

const isValidUserName = (userName) => {
  const nameRegex = /[^A-Za-z0-9]/ ;
  return (!nameRegex.test(userName) && userName.trim().length > 2);
}

const getAllDbUsers = async (socket) => {
  const usersDb = await User.find({})
  socket.emit('allDbUsers', usersDb) 
}


app.post('/login', async (req, res) => {
    try {
        const {userName,password} = req.body;
        if (!isValidUserName(userName)){
            return res.status(400).json({message: 'Invalid username'})
        }
        const dbUser = await getOneUser(userName)
        if (dbUser?.isBanned){
            return res.status(401).json({message: 'Your account has been banned!!!'})
        }
        const salt =await bcrypt.genSalt(2);
        const hashPassword = await bcrypt.hash(password, salt);
        if (!dbUser) {
            const user = new User({
                userName,
                hashPassword,
                isAdmin: !await User.count().exec(),
                isBanned: false,
                isMutted: false
            });
            await user.save()
            return res.json({
                token: generateToken(user.id, user.userName, user.isAdmin)
            });
        }

        if (dbUser && !bcrypt.compareSync(password, dbUser.hashPassword)){
            return res.status(400).json({message: 'Invalid credantials'})
        }
        res.json({
            token:  generateToken(dbUser.id, dbUser.userName, dbUser.isAdmin)
        })

    } catch (e) {
        console.log(e);
        res.status(500).json({message: `Error ${e}`});
    }
})

io.use(async (socket, next) => {
  const token = socket.handshake.auth.token;
  const sockets = await io.fetchSockets();
  if(!token) {
      socket.disconnect();
      return;
  }

  const usersOnline = [];
  sockets.map((sock) => {
      usersOnline.push(sock.user);
  }) 

 
  try {
      const user = jwt.verify(token, TOKEN_KEY);
      const userName = user.userName;
      const dbUser = await getOneUser(userName);
      if(dbUser.isBanned){
          socket.disconnect();
          return;
      }
      socket.user = user;
      const exist = sockets.find((current) => current.user.userName == socket.user.userName)

      if(exist) {  //&& !user.isAdmin  - add for two or more admins 
          console.log(exist.userName, 'exist twice entering...')   
          exist.disconnect(); 
      } 
  } catch(e) {
      console.log(e);
      socket.disconnect();
  }
  next();
});

io.on("connection", async (socket) => {
  const userName = socket.user.userName;
  const sockets = await io.fetchSockets();
  const dbUser = await getOneUser(userName);

  io.emit('usersOnline', sockets.map((sock) => sock.user)); // send array online users  
  socket.emit('connected', dbUser); //socket.user
 
  if(socket.user.isAdmin){
       getAllDbUsers(socket); 
  }//sent all users from db to admin

  const messagesToShow = await Message.find({}).sort({ 'createDate': -1 }).limit(20);

  socket.emit('allmessages', messagesToShow.reverse());
  socket.on("message", async (data) => {
      const dateNow = Date.now(); // for correct working latest post 
      const post = await Message.findOne({userName}).sort({ 'createDate': -1 })
      const oneUser = await getOneUser(userName);
      if(oneUser.isMutted){
          return;
      } 
      if(post && ((Date.now() - Date.parse(post?.createDate)) < 5000)){
          return;
      }
      if(!oneUser.isMutted && data){
      const message = new Message({
              text: data.message,
              userName: userName,
              createDate: Date.now()
          });
          try {
              await message.save(); 
              console.log('saved to db')
          } catch (error) {
              console.log('Message save to db error', error);   
          }
          io.emit('message', message);
      // }
     } 
  });
  
  try {
      socket.on("disconnect", async () => {
          const sockets = await io.fetchSockets();
          io.emit('usersOnline', sockets.map((sock) => sock.user));
          console.log(`user :${socket.user.userName} , disconnected to socket`); 
      });
          console.log(`user :${socket.user.userName} , connected to socket`); 
      
      socket.on("muteUser",async (data) => {
          if(!socket.user.isAdmin){
              return;
          }
              // if(socket.user.isAdmin){
                  const {user, prevStatus} = data;
                  const sockets = await io.fetchSockets();
                  const mute = await User.updateOne({userName : user}, {$set: {isMutted :!prevStatus}});
                  getAllDbUsers(socket);
                  const exist = sockets.find( current => current.user.userName == user)
                  const dbUser = await getOneUser(user);
                  
                  if(exist){
                      exist.emit('connected', dbUser);   
                  } 
              // }
         });
  
      socket.on("banUser",async (data) => {
          if(!socket.user.isAdmin){
              return;
          }
          // if(socket.user.isAdmin) { 
              const {user, prevStatus} = data;
              const sockets = await io.fetchSockets();
              const ban = await User.updateOne({userName : user}, {$set: {isBanned:!prevStatus}});
              getAllDbUsers(socket)
              const exist = sockets.find( current => current.user.userName == user)
              
              if(exist){
                  exist.disconnect();  
              }
          // }
         });
  } catch (e) {
      console.log(e);
  }
});
const start = async () => {
  try {
      await mongoose.connect('mongodb://localhost:27017/chat')
          .then(() => console.log(`DB started`))
      server.listen(PORT, () => {
          console.log(`Server started. Port: ${PORT}`);
      })
  } catch (e) {
      console.log(e);
  }
}

start();
