const express = require("express");
const app = express();
const cors = require("cors");
const jwt = require("jsonwebtoken");
const multer = require("multer"); // For file uploads
const path = require("path");
const { google } = require("googleapis");
const streamifier = require("streamifier");
const driveCredentials = require("./secrets/service-account.json");
const fs = require("fs"); // For file system operations
require("dotenv").config();
const port = process.env.PORT || 5000;

// MongoDB setup
const { MongoClient, ServerApiVersion, ObjectId } = require("mongodb");
const uri = `mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@cluster0.g9xsrko.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0`;

const client = new MongoClient(uri, {
  serverApi: {
    version: ServerApiVersion.v1,
    strict: true,
    deprecationErrors: true,
  },
});

// Set up CORS options to allow requests from your client
const corsOptions = {
  origin: ["http://localhost:5173", "https://bps.org.bd"], // Add multiple origins
  credentials: true, // Allow credentials (cookies, authorization headers)
};

const auth = new google.auth.JWT(
  driveCredentials.client_email,
  null,
  driveCredentials.private_key,
  ["https://www.googleapis.com/auth/drive"]
);

const drive = google.drive({ version: "v3", auth });

app.use(cors(corsOptions)); // Use CORS with options
app.use(express.json());
// Serve the uploaded files statically
app.use("/notices", express.static(path.join(__dirname, "uploads/notices")));

// Ensure the uploads directory exists (although this is not needed when using Cloudinary)
// Ensure the uploads directory exists
const uploadDir = "uploads/notices";
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}

// Multer configuration for uploading files
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, path.join(__dirname, "uploads", "notices")); // Upload to 'uploads/notices/'
  },
  filename: (req, file, cb) => {
    const fileName = Date.now() + path.extname(file.originalname);
    cb(null, fileName); // Use a unique filename
  },
});

// Multer memory storage configuration
const uploadPdf = multer({
  storage: multer.memoryStorage(), // Store in memory, no need to save locally
  limits: { fileSize: 10 * 1024 * 1024 }, // 10MB file size limit
  fileFilter: (req, file, cb) => {
    if (file.mimetype !== "application/pdf") {
      return cb(new Error("Only PDFs are allowed"));
    }
    cb(null, true);
  },
});

const verifyToken = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ message: "Authorization header missing" });
  }
  const token = authHeader.split(" ")[1];
  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, decoded) => {
    if (err) {
      return res.status(403).json({ message: "Invalid or expired token" });
    }
    req.user = decoded;
    next();
  });
};

async function run() {
  try {
    await client.connect();
    const membersCollection = client.db("bpsDB").collection("members");
    const galleryCollection = client.db("bpsDB").collection("gallery");
    const eventsCollection = client.db("bpsDB").collection("events");
    const noticeBoardCollection = client.db("bpsDB").collection("noticeBoard");
    const publicationsCollection = client
      .db("bpsDB")
      .collection("publications");
    const honoraryCollection = client.db("bpsDB").collection("honorary");
    const scMembersCollection = client.db("bpsDB").collection("scmembers");
    const ecMembersCollection = client.db("bpsDB").collection("ecmembers");

    // JWT API
    app.post("/jwt", async (req, res) => {
      const user = req.body;
      const token = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, {
        expiresIn: "1h",
      });
      res.send({ token });
    });

    app.post("/members", async (req, res) => {
      try {
        const newMember = req.body;
        console.log("Received new member data:", newMember); // Log request body

        // Validate required fields (optional step, but recommended)
        if (!newMember.email || !newMember.fullName || !newMember.password) {
          return res.status(400).json({ error: "Missing required fields" });
        }

        // Insert the new member into the database
        const result = await membersCollection.insertOne(newMember);

        // Check if the insertion was successful
        if (result.acknowledged) {
          res.status(201).json({
            message: "Member added successfully",
            memberId: result.insertedId,
          });
        } else {
          throw new Error("Failed to insert member into database");
        }
      } catch (error) {
        console.error("Error adding member:", error);
        res.status(500).json({ error: "Failed to add member" });
      }
    });

    // Endpoint to fetch all members
    app.get("/members", async (req, res) => {
      try {
        const members = await membersCollection.find({}).toArray();
        res.status(200).json(members);
      } catch (error) {
        console.error("Error fetching members:", error);
        res.status(500).json({ error: "Failed to fetch members" });
      }
    });

    // Endpoint to update the transaction ID (payment details)
    app.patch("/members/:id/payment", async (req, res) => {
      const userId = req.params.id;
      const { ddNo } = req.body; // New transaction ID

      try {
        // Find the user by ID
        const member = await membersCollection.findOne({ userId });

        if (!member) {
          return res.status(404).json({ error: "User not found" });
        }

        // If the member already has a payment object, update the history
        const existingPayment = member.payment || {};

        // Add current ddNo to the history before updating
        const updatedHistory = existingPayment.history || [];
        if (existingPayment.ddNo) {
          updatedHistory.push(existingPayment.ddNo); // Store the previous transaction ID
        }

        // Update the payment information with the new transaction ID and updated history
        const updatedPayment = {
          ...existingPayment,
          ddNo, // New transaction ID
          history: updatedHistory, // Updated transaction history
        };

        // Update the member document in the database
        await membersCollection.updateOne(
          { userId },
          { $set: { payment: updatedPayment } }
        );

        res
          .status(200)
          .json({ message: "Payment information updated successfully" });
      } catch (error) {
        console.error("Error updating payment information:", error);
        res.status(500).json({ error: "Failed to update payment information" });
      }
    });

    app.patch("/members/:id/admin-update", verifyToken, async (req, res) => {
      try {
        const userId = req.params.id;
        const { membershipId, status } = req.body;

        // Check if the requesting user is an admin
        const requestingUser = await membersCollection.findOne({
          email: req.user.email,
        });
        if (!requestingUser || requestingUser.role !== "admin") {
          return res.status(403).json({
            message: "You are not authorized to perform this action.",
          });
        }

        // Find the user to update
        const user = await membersCollection.findOne({
          _id: new ObjectId(userId),
        });
        if (!user) {
          return res.status(404).json({ message: "User not found." });
        }

        // Update membershipId and status fields
        const updateData = {};
        if (membershipId) updateData.membershipId = membershipId;
        if (status) updateData.status = status;

        const result = await membersCollection.updateOne(
          { _id: new ObjectId(userId) },
          { $set: updateData }
        );

        if (result.modifiedCount === 0) {
          return res
            .status(500)
            .json({ message: "Failed to update member data." });
        }

        res.status(200).json({ message: "Member data updated successfully." });
      } catch (error) {
        console.error("Error updating membership ID and status:", error);
        res.status(500).json({ error: "Failed to update member data." });
      }
    });

    // Endpoint to fetch a member by userId
    app.get("/members/user/:userId", async (req, res) => {
      try {
        const userId = req.params.userId;
        const member = await membersCollection.findOne({ userId });
        if (!member) {
          return res.status(404).json({ error: "Member not found" });
        }
        res.status(200).json(member);
      } catch (error) {
        console.error("Error fetching member:", error);
        res.status(500).json({ error: "Failed to fetch member" });
      }
    });

    // Admin check route, only to be used in admin-restricted sections
    app.get("/members/admin/:email", verifyToken, async (req, res) => {
      const email = req.params.email;
      try {
        const user = await membersCollection.findOne({ email });
        if (!user) {
          return res.status(404).json({ message: "User not found" });
        }
        const isAdmin = user.role === "admin"; // Check if the user's role is admin
        res.status(200).json({ admin: isAdmin });
      } catch (error) {
        console.error("Error checking admin status:", error);
        res.status(500).json({ error: "Failed to check admin status" });
      }
    });

    // Endpoint to toggle admin role
    app.patch("/members/:id/toggle-admin", async (req, res) => {
      try {
        const userId = req.params.id;
        const user = await membersCollection.findOne({
          _id: new ObjectId(userId),
        });

        if (!user) {
          return res.status(404).json({ error: "User not found" });
        }

        const newRole = user.role === "admin" ? "user" : "admin";

        await membersCollection.updateOne(
          { _id: new ObjectId(userId) },
          { $set: { role: newRole } }
        );

        res
          .status(200)
          .json({ message: `User role updated to ${newRole}`, newRole });
      } catch (error) {
        console.error("Error updating user role:", error);
        res.status(500).json({ error: "Failed to update user role" });
      }
    });

    // Endpoint to block a user
    app.patch("/members/:id/block", async (req, res) => {
      try {
        const userId = req.params.id;
        const user = await membersCollection.findOne({
          _id: new ObjectId(userId),
        });

        if (!user) {
          return res.status(404).json({ error: "User not found" });
        }

        const newStatus = !user.blocked;

        await membersCollection.updateOne(
          { _id: new ObjectId(userId) },
          { $set: { blocked: newStatus } }
        );

        res.status(200).json({
          message: `User ${newStatus ? "blocked" : "unblocked"} successfully`,
          newStatus,
        });
      } catch (error) {
        console.error("Error updating user status:", error);
        res.status(500).json({ error: "Failed to update user status" });
      }
    });

    // Endpoint to delete a user
    app.delete("/members/:id", async (req, res) => {
      try {
        const userId = req.params.id;
        const result = await membersCollection.deleteOne({
          _id: new ObjectId(userId),
        });

        if (result.deletedCount === 0) {
          return res.status(404).json({ error: "User not found" });
        }

        res.status(200).json({ message: "User deleted successfully" });
      } catch (error) {
        console.error("Error deleting user:", error);
        res.status(500).json({ error: "Failed to delete user" });
      }
    });

    app.get("/members", async (req, res) => {
      const { email } = req.query;
      try {
        const member = await membersCollection.findOne({ email });
        if (!member) {
          return res.status(404).json({ error: "User not found" });
        }
        res.status(200).json([member]);
      } catch (error) {
        console.error("Error fetching member", error);
        res.status(500).json({ error: "Failed to fetch member" });
      }
    });

    // Endpoint to fetch all EC members
    app.get("/ecmembers", async (req, res) => {
      try {
        const ecMembers = await ecMembersCollection.find({}).toArray();
        res.status(200).json(ecMembers);
      } catch (error) {
        console.error("Error fetching EC members:", error);
        res.status(500).json({ error: "Failed to fetch EC members" });
      }
    });

    // Endpoint to add a new EC member
    app.post("/ecmembers", verifyToken, async (req, res) => {
      try {
        const { designation, name, imageUrl } = req.body;

        if (!designation || !name || !imageUrl) {
          return res.status(400).json({ error: "All fields are required" });
        }

        const newECMember = { designation, name, imageUrl };

        // Insert into the EC members collection
        const result = await ecMembersCollection.insertOne(newECMember);

        if (!result.acknowledged) {
          return res.status(500).json({ error: "Failed to add EC member" });
        }

        res.status(201).json({ message: "EC member added successfully" });
      } catch (error) {
        console.error("Error adding EC member:", error);
        res.status(500).json({ error: "Failed to add EC member" });
      }
    });

    // Endpoint to delete an EC member
    app.delete("/ecmembers/:id", verifyToken, async (req, res) => {
      try {
        const ecMemberId = req.params.id;
        const result = await ecMembersCollection.deleteOne({
          _id: new ObjectId(ecMemberId),
        });

        if (result.deletedCount === 0) {
          return res.status(404).json({ error: "EC member not found" });
        }

        res.status(200).json({ message: "EC member deleted successfully" });
      } catch (error) {
        console.error("Error deleting EC member:", error);
        res.status(500).json({ error: "Failed to delete EC member" });
      }
    });

    // Endpoint to fetch all SC members
    app.get("/scmembers", async (req, res) => {
      try {
        const scMembers = await scMembersCollection.find({}).toArray();
        res.status(200).json(scMembers);
      } catch (error) {
        console.error("Error fetching SC members:", error);
        res.status(500).json({ error: "Failed to fetch SC members" });
      }
    });

    // Endpoint to add a new SC member
    app.post("/scmembers", verifyToken, async (req, res) => {
      try {
        const { designation, name, imageUrl } = req.body;

        if (!designation || !name || !imageUrl) {
          return res.status(400).json({ error: "All fields are required" });
        }

        const newSCMember = { designation, name, imageUrl };

        // Insert into the SC members collection
        const result = await scMembersCollection.insertOne(newSCMember);

        if (!result.acknowledged) {
          return res.status(500).json({ error: "Failed to add SC member" });
        }

        res.status(201).json({ message: "SC member added successfully" });
      } catch (error) {
        console.error("Error adding SC member:", error);
        res.status(500).json({ error: "Failed to add SC member" });
      }
    });

    // Endpoint to delete an SC member
    app.delete("/scmembers/:id", verifyToken, async (req, res) => {
      try {
        const scMemberId = req.params.id;
        const result = await scMembersCollection.deleteOne({
          _id: new ObjectId(scMemberId),
        });

        if (result.deletedCount === 0) {
          return res.status(404).json({ error: "SC member not found" });
        }

        res.status(200).json({ message: "SC member deleted successfully" });
      } catch (error) {
        console.error("Error deleting SC member:", error);
        res.status(500).json({ error: "Failed to delete SC member" });
      }
    });

    // Endpoint to fetch all gallery items
    app.get("/gallery", async (req, res) => {
      try {
        const galleryItems = await galleryCollection
          .find()
          .sort({ createdAt: -1 }) // Sort by createdAt in descending order
          .toArray();

        res.status(200).json(galleryItems);
      } catch (error) {
        console.error("Error fetching gallery items:", error);
        res.status(500).json({ error: "Failed to fetch gallery items" });
      }
    });

    // Endpoint to add a new gallery item
    app.post("/gallery", async (req, res) => {
      try {
        const { imageUrl, eventName, paragraph } = req.body;

        if (!imageUrl || !eventName || !paragraph) {
          return res.status(400).json({ error: "All fields are required" });
        }

        // Add createdAt timestamp
        const newGalleryItem = {
          imageUrl,
          eventName,
          paragraph,
          createdAt: new Date(), // Add a timestamp for sorting
        };

        await galleryCollection.insertOne(newGalleryItem);

        res.status(201).json({ message: "Gallery item added successfully" });
      } catch (error) {
        console.error("Error adding gallery item:", error);
        res.status(500).json({ error: "Failed to add gallery item" });
      }
    });

    // Endpoint to fetch all honorary members
    app.get("/honorary", async (req, res) => {
      try {
        const honoraryList = await honoraryCollection.find({}).toArray();
        res.status(200).json(honoraryList);
      } catch (error) {
        console.error("Error fetching honorary list:", error);
        res.status(500).json({ error: "Failed to fetch honorary list" });
      }
    });

    // Endpoint to add a new honorary member
    app.post("/honorary", verifyToken, async (req, res) => {
      try {
        const { year, name, position } = req.body;

        if (!year || !name || !position) {
          return res.status(400).json({ error: "All fields are required" });
        }

        const newHonoraryMember = { year, name, position };
        await honoraryCollection.insertOne(newHonoraryMember);

        res.status(201).json({ message: "Honorary member added successfully" });
      } catch (error) {
        console.error("Error adding honorary member:", error);
        res.status(500).json({ error: "Failed to add honorary member" });
      }
    });

    // Endpoint to delete an honorary member by ID
    app.delete("/honorary/:id", verifyToken, async (req, res) => {
      try {
        const honoraryId = req.params.id;
        const result = await honoraryCollection.deleteOne({
          _id: new ObjectId(honoraryId),
        });

        if (result.deletedCount === 0) {
          return res.status(404).json({ error: "Honorary member not found" });
        }

        res
          .status(200)
          .json({ message: "Honorary member deleted successfully" });
      } catch (error) {
        console.error("Error deleting honorary member:", error);
        res.status(500).json({ error: "Failed to delete honorary member" });
      }
    });

    // Endpoint to get all notices
    app.get("/notices", async (req, res) => {
      try {
        const notices = await noticeBoardCollection.find({}).toArray();
        res.status(200).json(notices);
      } catch (error) {
        console.error("Error fetching notices:", error);
        res.status(500).json({ error: "Failed to fetch notices" });
      }
    });

    app.post("/notices", uploadPdf.single("pdf"), async (req, res) => {
      if (!req.file) {
        return res.status(400).json({ error: "No file uploaded" });
      }

      try {
        // File metadata for Google Drive
        const fileMetadata = {
          name: req.file.originalname, // Use the original file name
          parents: ["1crdl0Fk1mrcozvs3ywaXszmmkZ285cW9"], // Google Drive folder ID where you want to upload the file
        };

        // Define media for the upload, streaming directly from the buffer
        const media = {
          mimeType: req.file.mimetype,
          body: streamifier.createReadStream(req.file.buffer), // Convert buffer to a readable stream
        };

        // Upload file to Google Drive
        const file = await drive.files.create({
          resource: fileMetadata,
          media: media,
          fields: "id, webViewLink, webContentLink",
        });

        // Set permissions to make the file accessible to anyone with the link
        await drive.permissions.create({
          fileId: file.data.id,
          requestBody: {
            role: "reader",
            type: "anyone",
          },
        });

        // Save file details in your MongoDB noticeBoardCollection
        const newNotice = {
          title: req.body.title || req.file.originalname, // Set title or use original filename
          driveFileId: file.data.id,
          webViewLink: file.data.webViewLink,
          uploadedAt: new Date(),
        };

        // Insert notice details into the database
        const insertResult = await noticeBoardCollection.insertOne(newNotice);
        res.status(201).json({
          message: "PDF uploaded to Google Drive and notice saved successfully",
          notice: newNotice,
          id: insertResult.insertedId,
        });
      } catch (error) {
        console.error("Error uploading to Google Drive:", error);
        res.status(500).json({ error: "Failed to upload to Google Drive" });
      }
    });

    // Serve the file via Google Drive link
    app.get("/notices/:id", async (req, res) => {
      try {
        const noticeId = req.params.id;
        const notice = await noticeBoardCollection.findOne({
          _id: new ObjectId(noticeId),
        });
        if (!notice) {
          return res.status(404).json({ error: "Notice not found" });
        }

        // Redirect to Google Drive webContentLink for download
        res.redirect(notice.webViewLink); // This link will open the PDF in Google Drive
      } catch (error) {
        console.error("Error serving file:", error);
        res.status(500).json({ error: "Failed to serve file" });
      }
    });

    app.delete("/notices/:id", async (req, res) => {
      const noticeId = req.params.id;
      try {
        const notice = await noticeBoardCollection.findOne({
          _id: new ObjectId(noticeId),
        });
        if (!notice) return res.status(404).json({ error: "Notice not found" });

        // Delete the file from Google Drive
        await drive.files.delete({ fileId: notice.driveFileId });

        // Delete the notice from MongoDB
        await noticeBoardCollection.deleteOne({ _id: new ObjectId(noticeId) });

        res
          .status(200)
          .json({ message: "Notice and file deleted successfully" });
      } catch (error) {
        console.error("Error deleting notice and file:", error);
        res.status(500).json({ error: "Failed to delete notice and file" });
      }
    });

    // Upload PDF to Google Drive and save metadata to MongoDB
    app.post("/publications", uploadPdf.single("pdfFile"), async (req, res) => {
      try {
        const file = req.file;

        if (!file) {
          return res.status(400).json({ error: "No file uploaded" });
        }

        // Stream the file buffer directly to Google Drive
        const fileMetadata = {
          name: file.originalname,
          parents: ["1crdl0Fk1mrcozvs3ywaXszmmkZ285cW9"], // Same folder as Noticeboard
        };

        const media = {
          mimeType: file.mimetype,
          body: streamifier.createReadStream(file.buffer), // Stream from buffer
        };

        // Upload the file to Google Drive
        const driveFile = await drive.files.create({
          resource: fileMetadata,
          media: media,
          fields: "id, webViewLink, webContentLink",
        });

        // Make the file publicly accessible
        await drive.permissions.create({
          fileId: driveFile.data.id,
          requestBody: {
            role: "reader",
            type: "anyone",
          },
        });

        // Save the file metadata to MongoDB
        const newPublication = {
          title: req.body.title || file.originalname,
          driveFileId: driveFile.data.id,
          webViewLink: driveFile.data.webViewLink,
          uploadedAt: new Date(),
        };

        const result = await publicationsCollection.insertOne(newPublication);
        res.status(201).json({
          message:
            "PDF uploaded to Google Drive and publication saved successfully",
          publication: newPublication,
          id: result.insertedId,
        });
      } catch (error) {
        console.error("Error uploading to Google Drive:", error);
        res.status(500).json({ error: "Failed to upload to Google Drive" });
      }
    });

    // Endpoint to fetch all publications
    app.get("/publications", async (req, res) => {
      try {
        const publications = await publicationsCollection
          .find({})
          .sort({ uploadedAt: -1 }) // Sort by uploadedAt in descending order
          .toArray();
        res.status(200).json(publications);
      } catch (error) {
        console.error("Error fetching publications:", error);
        res.status(500).json({ error: "Failed to fetch publications" });
      }
    });

    app.delete("/publications/:id", async (req, res) => {
      const publicationId = req.params.id;
      try {
        const publication = await publicationsCollection.findOne({
          _id: new ObjectId(publicationId),
        });
        if (!publication) {
          return res.status(404).json({ error: "Publication not found" });
        }

        // Delete the file from Google Drive
        await drive.files.delete({ fileId: publication.driveFileId });

        // Delete the publication from MongoDB
        await publicationsCollection.deleteOne({
          _id: new ObjectId(publicationId),
        });

        res
          .status(200)
          .json({ message: "Publication and file deleted successfully" });
      } catch (error) {
        console.error("Error deleting publication:", error);
        res.status(500).json({ error: "Failed to delete publication" });
      }
    });

    // Endpoint to fetch all events
    app.get("/events", async (req, res) => {
      try {
        // Sort by createdAt in descending order to get latest events on top
        const events = await eventsCollection
          .find({})
          .sort({ createdAt: -1 })
          .toArray();
        res.status(200).json(events);
      } catch (error) {
        console.error("Error fetching events:", error);
        res.status(500).json({ error: "Failed to fetch events" });
      }
    });

    // Endpoint to add a new event
    app.post("/events", async (req, res) => {
      try {
        const { eventName, paragraph, images } = req.body;

        if (!eventName || !paragraph || !images || !Array.isArray(images)) {
          return res.status(400).json({
            error: "All fields are required and images must be an array",
          });
        }

        const newEvent = {
          eventName,
          paragraph,
          images,
          createdAt: new Date(), // Add timestamp when creating a new event
        };
        await eventsCollection.insertOne(newEvent);

        res.status(201).json({ message: "Event added successfully" });
      } catch (error) {
        console.error("Error adding event:", error);
        res.status(500).json({ error: "Failed to add event" });
      }
    });

    // Endpoint to delete an event
    app.delete("/events/:id", async (req, res) => {
      try {
        const eventId = req.params.id;
        const event = await eventsCollection.findOne({
          _id: new ObjectId(eventId),
        });

        if (!event) {
          return res.status(404).json({ error: "Event not found" });
        }

        await eventsCollection.deleteOne({ _id: new ObjectId(eventId) });

        res.status(200).json({ message: "Event deleted successfully" });
      } catch (error) {
        console.error("Error deleting event:", error);
        res.status(500).json({ error: "Failed to delete event" });
      }
    });
  } finally {
    // Ensure the client will close when you finish/error
    // await client.close();
  }
}
run().catch(console.dir);

app.get("/", (req, res) => {
  res.send("bps server");
});

app.listen(port, () => {
  console.log(`bps server is running on port ${port}`);
});
