const express = require("express");
const { createServer } = require("http");
const app = express();
const cors = require("cors");
const server = createServer(app);
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const bcrypt = require("bcrypt");
const crypto = require("crypto");
const saltRounds = 10;
const { Server } = require("socket.io");
const io = new Server(server);
require("dotenv").config();
const PORT = process.env.SERVER_PORT;
const {
  initializeDatabaseConnection,
  client,
  insertUser,
  insertMessage,
  checkUserExist,
  changePassword,
  getPassword,
  getUserId,
  deleteContact,
  updateContactStatus,
  getMessages,
} = require("./db/db.js");
const authorizeUser = require("./utils/authorize");
const filter = require("./utils/filter");
const { generateJwtToken, verifyJwtToken } = require("./auth/jwt");
const { initializeSocket } = require("./socket/socket");
const { getContacts, insertContact } = require("./db/db");

const corsOptions = {
  origin: process.env.ORIGIN,
  optionsSuccessStatus: 200,
  credentials: true,
};

// Serve socket.io js
app.use("/socket.io", express.static("./node_modules/socket.io/client-dist/"));
app.use(cors(corsOptions));
app.use(bodyParser.json());
app.use(cookieParser());

app.post("/api/auth/signup", async (req, res) => {
  try {
    const username = req.body.username.trim().replace(/[^a-zA-Z0-9]/g, "");
    const password = req.body.password;
    console.log(username);
    // Validate form data length
    if (!username || username.length < 4 || username.length > 20) {
      return res.status(400).json({ message: "Invalid username length" });
    }
    if (!password || password.length < 8 || password.length > 128) {
      return res.status(400).json({ message: "Invalid password length" });
    }

    // Checks if the user already exists in database (returns result.rows[0].count > 0;)
    const exist = await checkUserExist(username);
    if (exist) {
      return res.status(409).json({ message: "User already exist" });
    }

    // Hash password and insert hash and username to database
    const hash = await bcrypt.hash(password, saltRounds);

    await insertUser(username, hash);
    const user_id = await getUserId(username);

    // Set JWT token to cookies
    const token = generateJwtToken(username, user_id);
    res.cookie("token", token, {
      maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
    });

    return res.status(200).json({ message: "Successfully signed up" });
  } catch (e) {
    console.error("Signup error: ", e);
    return res.status(500).json({ message: "internal server error" });
  }
});

app.post("/api/auth/login", async (req, res) => {
  try {
    const username = req.body.username.trim().toLowerCase();
    const password = req.body.password;

    if (
      !username ||
      !password ||
      username.length < 4 ||
      username.length > 20 ||
      password.length < 8 ||
      password.length > 128
    ) {
      return res.status(400).json({ message: "Invalid credentials" });
    }

    // Checks if the user exist
    const exist = await checkUserExist(username);
    if (!exist) {
      return res.status(404).json({ message: "User does not exist" });
    }
    const hashedPassword = await getPassword(username);
    // Compare passwords
    bcrypt
      .compare(password, hashedPassword)
      .then(async (result) => {
        if (!result) {
          res.status(401).json({ message: "Invalid password" });
          return;
        }
        const user_id = await getUserId(username);
        const token = generateJwtToken(username, user_id);
        res.cookie("token", token, {
          maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
        });
        return res.status(200).json({ message: "Successfully logged In" });
      })
      .catch((err) => {
        return res.status(500).json({ message: "Internal server error" });
      });
  } catch (e) {
    console.error("Login error: ", e);
    return res.status(500).json({ message: "Internal server error" });
  }
});

app.get("/api/auth/validate", authorizeUser, (req, res) => {
  return res.status(200).json({
    message: "Authorized",
    username: req.user.username,
  });
});

app.delete("/api/chat/contacts/:contact", authorizeUser, async (req, res) => {
  if (!req.params.contact) {
    return res
      .status(400)
      .json({ message: "Missing usernameContact parameter" });
  }
  const usernameContact = filter(req.params.contact);

  await deleteContact(req.user.username, usernameContact);
  return res.status(200).json({ message: "Successfully deleted contact" });
});

app.put("/api/chat/contacts/:contact", authorizeUser, async (req, res) => {
  if (!req.params.contact) {
    return res
      .status(400)
      .json({ message: "Missing usernameContact parameter" });
  }
  const usernameContact = filter(req.params.contact);
  const read = req.body.status;
  await updateContactStatus(req.user.username, usernameContact, read);

  return res
    .status(200)
    .json({ message: "Successfully updated contact status" });
});

app.post("/api/chat/contact/:contact", authorizeUser, async (req, res) => {
  if (!req.params.contact) {
    return res.status(400).json({ message: "Missing contact parameter" });
  }
  const usernameContact = filter(req.params.contact);
  await insertContact(req.user.username, usernameContact, true);

  return res.status(200).json({ message: "Successfully inserted contact" });
});

app.get("/api/chat/contacts", authorizeUser, async (req, res) => {
  if (!req.user.username) {
    return res.status(401).json({
      message: "Missing username (that's weird you shouldn't see that)",
    });
  }
  const contacts = await getContacts(req.user.username);
  console.log("Sent contacts list for: ", req.user.username);
  return res.status(200).json(contacts);
});

app.get("/api/chat/messages/:contact", authorizeUser, async (req, res) => {
  if (!req.params.contact) {
    return res.status(400).json({ message: "Missing contact parameter" });
  }
  const limit = parseInt(req.query.limit);
  const cursor = parseInt(req.query.cursor);

  const messages = await getMessages(
    req.user.username,
    req.params.contact,
    limit,
    cursor,
  );
  if (messages && messages.length < limit) {
    return res.status(404).json({ message: "No more messages found" });
  }

  console.log("MESSAGESLENGTH: ", messages.length, limit);
  console.log("Sent messages for: ", req.user.username, "messages: ", messages);
  return res.status(200).json({ messages });
});

initializeSocket(io);

server.listen(PORT, () => {
  console.log(`Server is running on port: ${PORT}`);
});
