const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const session = require("express-session");
const passport = require("passport");
const bodyParser = require("body-parser");
const path = require("path");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const cookieparser = require("cookie-parser");
require("dotenv").config();

// Import Routes
const authRoutes = require("./routes/authRoutes");
const courseRoutes = require("./routes/courseRoutes");
const facultyRoutes = require("./routes/facultyRoutes");
const appointmentRoutes = require("./routes/appointmentRoutes");
const optionsRoutes = require("./routes/optionsRoutes");
const consolidatedRoutes = require("./routes/consolidatedRoutes");
const emailRoutes = require("./routes/emailRoutes");
const Course = require("./models/Course");
const User = require("./models/User");

// MongoDB Connection
// MongoDB Connection
const connectDB = async () => {
  try {
    await mongoose.connect(process.env.mongoURI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log("MongoDB connected");
  } catch (err) {
    console.error("MongoDB connection failed:", err.message);
  }
};

connectDB();

// Initialize App
const app = express();
const PORT = 8080;

// Middleware
app.use(
  cors({
    origin: process.env.CORS_ORIGIN || "http://localhost:3000",
    credentials: true,
  })
);
app.use(cookieparser());
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(
  session({
    secret: "secret", // This can be replaced with another secret from .env if required
    resave: false,
    saveUninitialized: false,
  })
);
app.use(passport.initialize());
app.use(passport.session());

app.use("/api/table", consolidatedRoutes);

// Passport Config
require("./config/passport");

// Routes
app.use("/password", authRoutes);

app.use("/api/courses", courseRoutes);
app.use("/api/faculty", facultyRoutes);
app.use("/api/appointments", appointmentRoutes);
app.use("/api/options", optionsRoutes);
app.use("/api/data", consolidatedRoutes);
app.use("/api/send-email", emailRoutes);

// Google OAuth Routes
app.get(
  "/auth/google",
  passport.authenticate("google", { scope: ["profile", "email"] })
);

app.get(
  "/auth/google/callback",
  passport.authenticate("google", { failureRedirect: "/" }),
  (req, res) => {
    const token = jwt.sign(
      { userId: req.user._id, isAdmin: req.user.isAdmin },
      process.env.JWT_SECRET,
      {
        expiresIn: "1h",
      }
    );
    // Set token as a cookie or send it in the response
    res.cookie("token", token, {
      httpOnly: true,
      secure: false,
      maxAge: 3600000,
    });
    res.redirect("http://localhost:3000/Welcome"); // Redirect to a frontend route after successful login
  }
);

// Local authentication routes (register and login)
app.post("/api/register", async (req, res) => {
  try {
    const { username, email, password } = req.body;

    // Validation: Only allow somaiya emails
    if (email.endsWith("@somaiya.edu") === false) {
      return res
        .status(400)
        .json({ message: "Only @somaiya.edu emails are allowed" });
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    let user = await User.findOne({ email });

    if (user) {
      if (user.googleId && user.password) {
        return res.status(400).json({ message: "User already exists" });
      }
      if (!user.googleId && user.password) {
        return res.status(400).json({ message: "User already exists" });
      }
      if (user.googleId && !user.password) {
        user.password = hashedPassword;
        await user.save();
      }
    } else {
      user = new User({ username, email, password: hashedPassword });
      await user.save();
    }

    // adding isAdmin to token so we know if user is admin
    const token = jwt.sign(
      { userId: user._id, isAdmin: user.isAdmin },
      process.env.JWT_SECRET,
      {
        expiresIn: "1h",
      }
    );

    // Set the token as a cookie
    res.cookie("token", token, {
      httpOnly: true,
      secure: false,
      maxAge: 3600000,
    }); // 1 hour expiry

    return res.status(200).json({
      message: "Registered and logged in successfully",
      user,
    });
  } catch (error) {
    console.error("Error registering user:", error);
    res.status(400).send("Registration failed");
  }
});

app.post("/api/login", (req, res, next) => {
  passport.authenticate("local", (err, user, info) => {
    if (err) {
      return res.status(500).json({ message: "Internal server error" });
    }
    if (!user) {
      return res.status(401).json({ message: "Incorrect email or password" });
    }
    req.logIn(user, (err) => {
      if (err) {
        return res.status(500).json({ message: "Internal server error" });
      }
      // Generate a JWT token using the user's ID
      const token = jwt.sign(
        { userId: user._id, isAdmin: user.isAdmin },
        process.env.JWT_SECRET,
        {
          expiresIn: "1h",
        }
      );

      // Set the token as a cookie
      res.cookie("token", token, {
        httpOnly: true,
        secure: false,
        maxAge: 3600000,
      }); // 1 hour expiry
      return res.status(200).json({ message: "Login successful", user });
    });
  })(req, res, next);
});

app.get("/auth/logout", function (req, res) {
  try {
    // Clear the token cookie
    res.clearCookie("token", {
      httpOnly: true, // Ensure it matches the cookie options you set earlier
      secure: false, // Match the "secure" option from cookie settings
      sameSite: "lax", // Ensure this matches the original cookie configuration
    });

    // Destroy the session if used (optional, if sessions are implemented)
    if (req.session) {
      req.session.destroy((err) => {
        if (err) {
          console.error("Error destroying session:", err);
          return res.status(500).json({ message: "Error logging out" });
        }
        res
          .status(200)
          .json({ message: "Logout successful, session destroyed" });
      });
    } else {
      // If no session, simply respond with success
      res.status(200).json({ message: "Logout successful, cookie cleared" });
    }
  } catch (err) {
    console.error("Error logging out:", err);
    res.status(500).json({ message: "Error logging out" });
  }
});

// Refresh Token Endpoint
app.post("/api/refresh", (req, res) => {
  const refreshToken = req.cookies.token;

  if (!refreshToken) {
    return res
      .status(401)
      .json({ message: "No refresh token, authorization denied" });
  }

  try {
    const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET);
    const newToken = jwt.sign(
      { userId: decoded.userId, isAdmin: decoded.isAdmin },
      process.env.JWT_SECRET,
      { expiresIn: "1h" }
    );

    return res
      .cookie("token", newToken, { httpOnly: true, maxAge: 3600000 }) // Set new access token
      .status(200)
      .json({ message: "Token refreshed" });
  } catch (err) {
    console.error("Error refreshing token:", err);
    res.status(401).json({ message: "Invalid or expired refresh token" });
  }
});

app.get("/api/auth-check", (req, res) => {
  const token = req.cookies.token; // Retrieve the httpOnly cookie

  if (!token) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  try {
    jwt.verify(token, process.env.JWT_SECRET); // Verify the token
    res.status(200).json({ authenticated: true }); // Valid token
  } catch (err) {
    res.status(401).json({ message: "Unauthorized" }); // Invalid token
  }
});

app.get("/api/me", async (req, res) => {
  try {
    const token = req.cookies.token; // ✅ Get token from request cookies
    if (!token)
      return res.status(401).json({ message: "Unauthorized - No Token" });

    const decoded = jwt.verify(token, process.env.JWT_SECRET); // ✅ Verify token

    // ✅ Fetch user from DB to ensure latest `isAdmin` value
    const user = await User.findById(decoded.userId);
    if (!user) return res.status(404).json({ message: "User not found" });

    res.json({
      userId: user._id,
      isAdmin: user.isAdmin, // ✅ Return actual `isAdmin` value
      exp: decoded.exp,
      iat: decoded.iat,
    });
  } catch (error) {
    console.error("JWT Verification Error:", error.message);
    res.status(401).json({ message: "Invalid token" });
  }
});

// User Profile Route
app.get("/api/user/profile", async (req, res) => {
  try {
    if (req.user) {
      return res.json({ user: req.user });
    } else {
      return res.status(401).json({ error: "Unauthorized" });
    }
  } catch (error) {
    console.error("Error fetching user data:", error);
    res.status(500).json({ message: "Internal server error" });
  }
});

app.patch("/api/courses/:courseId", async (req, res) => {
  const { courseId } = req.params;
  const { status } = req.body;

  if (!status) {
    console.error("Status is missing in the request body.");
    return res.status(400).json({ message: "Status is required" });
  }

  try {
    const updatedCourse = await Course.findOneAndUpdate(
      { courseId: courseId }, // Use courseId field for finding the course
      { status }, // Update the status field
      { new: true } // Return the updated document
    );

    if (!updatedCourse) {
      console.error("Course not found:", courseId);
      return res.status(404).json({ message: "Course not found" });
    }

    res.json(updatedCourse);
  } catch (error) {
    console.error("Error updating course status:", error.message);
    res.status(500).json({ message: "Internal server error" });
  }
});

// Serve React Build Files
app.use(express.static(path.join(__dirname, "../client/build")));
app.get("*", (req, res) =>
  res.sendFile(path.join(__dirname, "../client/build/index.html"))
);

// Error Handling Middleware
app.use((err, req, res, next) => {
  console.error("Error:", err.stack);
  res
    .status(err.status || 500)
    .json({ error: err.message || "Internal Server Error" });
});

// Start Server
// Start Server
if (require.main === module) {
  app.listen(PORT, () => {
    console.log(`Server is running at http://localhost:8080`);
  });
}

module.exports = app;
