const express = require("express");
const fs = require("node:fs");
const path = require("node:path");
const supabase = require("@supabase/supabase-js");
const cookie_parser = require("cookie-parser");
const multer = require("multer");
const app = express();
require("dotenv").config();

const webhookURL = process.env.WEBHOOK_URL;
const utils = require("./utils.js");
const url = "https://skibidihub.buttplugstudios.xyz"

let ipBlacklist = JSON.parse(fs.readFileSync("./ipbans.json"));
fs.watch("./ipbans.json", () => {
  console.log("Reloading IP Bans...");
  ipBlacklist = JSON.parse(fs.readFileSync("./ipbans.json"));
});

const port = 3000;
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    try {
      // Validations
      if (!utils.checkBodyVideo(req.body)) throw new Error("invalid video body");
      if (!utils.checkToken(req, "/api/upload multer")) throw new Error("unauthorized");

      if(!req.skibidihub_id) req.skibidihub_id = utils.nanoid(7);
      console.log("Video ID:", req.skibidihub_id);

      // Check if the directory exists, and create it if it doesn't
      const dir = path.join(__dirname, "videos", req.skibidihub_id);
      if (!utils.videoExists(req.skibidihub_id)) {
        console.log("Directory does not exist, creating:", dir);
        fs.mkdirSync(dir, { recursive: true });
      }

      // Set the destination path for multer
      cb(null, dir);
    } catch (err) {
      console.log("Error in destination function:", err);
      cb(err);
    }
  },
  filename: function (req, file, cb) {
    let filename;
    if (file.fieldname === "video") {
      filename = "video.mp4";
    } else if (file.fieldname === "thumbnail") {
      filename = "thumbnail.jpg";
    }
    console.log("Saving file with filename:", filename);
    cb(null, filename);
  }
});

const upload = multer({ 
  storage: storage,
  limits: { fileSize: 1000000 * 250 /* 250MB in bytes */ }
});

const client = supabase.createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY
);

const ipwareObject = require("@fullerstack/nax-ipware");
const sunset = JSON.parse(fs.readFileSync(path.join(__dirname, "sunset.json")));
const ipware = new ipwareObject.Ipware();
app.use(function(req, res, next) {
  req.ipInfo = ipware.getClientIP(req);

  if (new Date().getTime() >= sunset.timestamp && sunset.sunset == true) {
    if(req.path == "/assets/piss baby.mp4") {
      if (ipBlacklist.includes(req.ipInfo.ip)) {
        res.setHeader("Cache-Control", "no-cache");
        return res.sendFile(path.join(__dirname, path.join("www", "down.html")));
      }      
    } else {
    return res.sendFile(path.join(__dirname, path.join("www", "sunset.html")));
    }
  }

  if(ipBlacklist.includes(req.ipInfo.ip)) {
    res.setHeader("Cache-Control", "no-cache");
    return res.sendFile(path.join(__dirname, path.join("www", "down.html")));
  }

  next();
});

app.use(express.static("www")); // Static folder
app.use(express.json()); // JSON body parser
app.use(cookie_parser()); // Cookie parser

// User facing URL's
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, path.join("www", "index.html")));
});

app.get("/login", (req, res) => {
  res.sendFile(path.join(__dirname, path.join("www", "login.html")));
});

app.get("/upload", (req, res) => {
  res.sendFile(path.join(__dirname, path.join("www", "upload.html")));
})

app.get("/contact", (req, res) => {
  res.sendFile(path.join(__dirname, path.join("www", "contact.html")));
})

app.get("/editProfile", (req, res) => {
  res.sendFile(path.join(__dirname, path.join("www", "editProfile.html")));
});

app.get("/search", (req, res) => {
  res.sendFile(path.join(__dirname, path.join("www", "search.html")));
});

app.set('view engine', 'ejs');
app.get("/video/:id", async (req, res) => {
  if(utils.discordCheck(req) && utils.videoExists(req.params.id)) {
    const videoInfo = await utils.videoInfo(req.params.id);
    if(isNaN(videoInfo)) {
      return res.render("video", {
        video: `${url}/api/video/${req.params.id}.mp4`,
        video_name: videoInfo.title,
        description: videoInfo.description,
        url: url,
        author_url: `${url}/api/oembed/?author_name=${videoInfo.uploader}&author_url=${url}/user/${encodeURIComponent(videoInfo.uploader)}`
      })
    }
  }

  if(!utils.videoExists(req.params.id) && utils.checkToken(req, "/video/:id")) {
    return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
  }

  res.sendFile(path.join(__dirname, path.join("www", "video.html")));
});

app.get("/user/:user", (req, res) => {
  if(!utils.checkToken(req, "/user/:user")) {
    return res.sendFile(path.join(__dirname, path.join("www", "user.html")));
  }

  // Check if user exists
  client.from("users").select().eq("name", decodeURIComponent(req.params.user)).then(data => {
    if(data.error) {
      return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
    } else if (data.status != 200) {
      return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
    }

    if(!data.data[0]) return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
    res.sendFile(path.join(__dirname, path.join("www", "user.html")));
  })
});

// API //

function handleVideoAPI(req, res) {
  if(!utils.checkToken(req, "/api/video/:id") && !utils.discordCheck(req)) {
    return res.sendFile(
      path.join(
        __dirname,
        path.join("www", path.join("assets", path.join("troll", "video.mp4")))
      )
    );
  }

  const videoPath = path.join(__dirname, path.join("videos", path.join(req.params.id, "video.mp4")));
  if(!fs.existsSync(videoPath)) return res.sendStatus(404);

  const stat = fs.statSync(videoPath);
  const fileSize = stat.size;
  const range = req.headers.range;

  console.log('Requested Range:', range); // Log the range for debugging

  if (range) {
    const parts = range.replace(/bytes=/, "").split("-");
    let start = parseInt(parts[0], 10);
    let end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;

    // Handle the case where range is 0-1
    if (start === 0 && end === 1) {
      end = 1;
    }

    const chunksize = (end - start) + 1;
    const file = fs.createReadStream(videoPath, {start, end});
    const head = {
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': chunksize,
      'Content-Type': 'video/mp4',
    };
    res.writeHead(206, head);
    file.pipe(res);
  } else {
    const head = {
      'Content-Length': fileSize,
      'Content-Type': 'video/mp4',
      'Accept-Ranges': 'bytes',
    };
    res.writeHead(200, head);
    fs.createReadStream(videoPath).pipe(res);
  }
}

// Get an mp4 file according to its video ID.
app.get('/api/video/:id*.mp4', handleVideoAPI);
app.get("/api/video/:id", handleVideoAPI);

// OEmbed
const oembed = (provider_name, provider_url, author_name, author_url, url) => {
  const baseObject = {
      version: '1.0',
  };
  if (provider_name && provider_url) {
      baseObject.provider_name = provider_name;
      baseObject.provider_url = provider_url;
  }
  if (author_name) {
      baseObject.author_name = author_name;
  }
  if (author_url) {
      baseObject.author_url = author_url;
  }
  if (url) {
      baseObject.url = url;
  }
  return baseObject;
};

app.get('/api/oembed', (req, res) => {
  const {
      provider_name,
      provider_url,
      author_name,
      author_url,
      url,
  } = req.query;
  return res.status(200).send(oembed(provider_name, provider_url, author_name, author_url, url));
});

// Get a videos thumbnail according to its video ID.
app.get("/api/thumbnail/:id", (req, res) => {
  if (!utils.checkToken(req, "/api/thumbnail/:id")) {
    let images = fs.readdirSync(
      path.join(__dirname, path.join("www", path.join("assets", "troll")))
    );
    res.sendFile(
      path.join(
        __dirname,
        path.join(
          "www",
          path.join(
            "assets",
            path.join("troll", images[utils.getRandomInt(images.length - 1)])
          )
        )
      )
    );
    return;
  }

  const thumbnail = utils.getThumbnail(req.params.id);
  if(thumbnail) return res.sendFile(thumbnail);
  if(!thumbnail) return res.sendStatus(404);
});

// Get a videos thumbnail according to its video ID.
app.get("/api/webhookThumbnail/:id", (req, res) => {
  const thumbnail = utils.getThumbnail(req.params.id);
  if(thumbnail) return res.sendFile(thumbnail);
  if(!thumbnail) return res.sendStatus(404);
});

// Get the info for a video according to its video ID.
app.get("/api/videoInfo/:id", async (req, res) => {
  if(!utils.checkToken(req, "/api/videoInfo/:id")) {
    let newData = {};
    newData.title = utils.fakeTitleList[utils.getRandomInt(utils.fakeTitleList.length)]
    newData.description = "SIGN IN to see this EPIC content"
    newData.likes = "69"
    newData.dislikes = "0"
    newData.uploader = "SIGN IN to see this EPIC content"
    newData.uploaded_at = new Date().toISOString().toString();
    return res.send(newData);
  }

  if(!utils.videoExists(req.params.id)) return res.sendStatus(404);

  const videoInfo = await utils.videoInfo(req.params.id);
  if(isNaN(videoInfo)) {
    return res.send(videoInfo);
  } else {
    return res.sendStatus(videoInfo);
  }
});

// Pulls the user information from the database and returns it.
app.get("/api/userInfo/:id", (req, res) => {
  if(!utils.checkToken(req, "/api/userInfo/:id")) {
    let data = {
      name: "SIGN IN to see this EPIC CONTENT",
      subscribers: 999,
      social_score: 999,
      description: "SIGN IN to see this EPIC content",
      verified: true
    }

    return res.send(data);
  }


  client.from("users").select().eq("name", decodeURIComponent(req.params.id)).then(data => {
    if(data.error) {
      return res.sendStatus(400);
    } else if (data.status != 200) {
      return res.sendStatus(data.status);
    }

    if(!data.data[0]) return res.sendStatus(400);
    return res.send(data.data[0]);
  })
})

// Subscribes to a user
app.get("/api/subscribe/:id", async (req, res) => {
  if(!utils.checkToken(req)) return res.sendStatus(401);
  if(!utils.userExists(decodeURIComponent(req.params.id))) return res.sendStatus(400);

  const subscribersData = await client
    .from("users")
    .select("subscribers")
    .eq("name", decodeURIComponent(req.params.id));

  const subscribers = subscribersData["data"][0]["subscribers"] + 1;

  client.from("users").update({
    subscribers: subscribers
  }).eq("name", decodeURIComponent(req.params.id)).then(data => {
    if(data.error) {
      return res.sendStatus(400);
    } else if (data.status != 204) {
      return res.sendStatus(data.status);
    }

    return res.sendStatus(200);
  })
});

// Login endpoints (Just handles user creation)
app.post("/api/login", async (req, res) => {
  if(!req.body.user) return res.sendStatus(400);
  const ipInfo = ipware.getClientIP(req);

  // If user doesn't exist, create the user.
  if(!await utils.userExists(req.body.user)) {
    console.log(`User ${req.body.user} doesn't exist. Creating the user...`);
    await client.from("users").insert({
      name: req.body.user
    }).then(data => {
      if(data.error) {
        return res.status(500).send(data);
      } else if(data.status != 201) {
        return res.status(500).send(data);
      }

      console.log(`User created successfully. IP: ${ipInfo.ip}`);
      return res.sendStatus(200);
    });
  } else {
    console.log(`User ${req.body.user} already exists. Logging in... IP: ${ipInfo.ip}`);
    return res.sendStatus(200);
  }
});

app.post("/api/editUser", async (req, res) => {
  // Validate request
  const user = utils.checkUserToken(req, "/api/editUser");
  if(!user.value) return res.status(401).json({message: "you need to login to edit your user page"});
  if(!req.body.description && !req.body.website) return res.status(400).json({message: "invalid form data"});
  if(req.body.website && !utils.isValidUrl(req.body.website)) return res.status(400).json({message: "invalid website"});
  
  // Update user
  await client.from("users").update({
    description: req.body.description,
    website: req.body.website,
  }).eq("name", user.user).then(data => {
    if(data.error) {
      return res.status(500).send(data);
    } else if(data.status != 204) {
      return res.status(500).send(data);
    }

    return res.sendStatus(200);
  })
});

// Get the comments for a video according to its video ID.
app.get("/api/comments/:videoID", (req, res) => {
  if(!utils.checkToken(req, "/api/comments/:videoID")) {
    let comments = [];
    for(let i = 0; i < 8; i++) {
      let comment = {};
      comment.text = utils.fakeCommentList[utils.getRandomInt(utils.fakeCommentList.length)]
      comment.commenter = "SIGN IN to see this EPIC content!";
      let date = new Date();
      date.setTime(1005286084);
      comment.created_at = date.toISOString();
      comments.push(comment);
    }

    return res.send(comments);
  }

  if(!utils.videoExists(req.params.videoID)) return res.sendStatus(404);

  client
    .from("comments")
    .select()
    .eq("video_id", req.params.videoID)
    .then((data) => {
      if (data.error) {
        res.sendStatus(400);
      } else if (data.status != 200) {
        res.sendStatus(data.status);
      }
      
      res.send(data["data"]);
    });
});

// Get a list of all videos
app.get("/api/getAllVideos", (req, res) => {
  if(!utils.checkToken(req, "/api/getAllVideos/")) {
    let newData = [];
    for(let i = 0; i < 12; i++) {
      let temp = {};
      temp.description = "SIGN IN to see this EPIC content"
      temp.title = utils.fakeTitleList[utils.getRandomInt(utils.fakeTitleList.length)]
      temp.uploader = "SIGN IN to see this EPIC content";
      temp.id = utils.nanoid(7);
      newData.push(temp);
    }

    return res.send(newData);
  }

  client
    .from("videos")
    .select()
    .then((data) => {
      if (data.error) {
        return res.sendStatus(400);
      } else if (data.status != 200) {
        return res.sendStatus(data.status);
      }

      return res.send(data["data"]);
    });
});

// Search endpoint
app.post("/api/search", (req, res) => {
  if(!utils.checkToken(req)) return res.sendStatus(401);
  if(!req.body.query) return res.sendStatus(400);
  client
  .from("videos")
  .select()
  .then((response) => {
    if (response.error) {
      return res.sendStatus(400);
    } else if (response.status != 200) {
      return res.sendStatus(response.status);
    }

    let results = [];
    response.data.forEach(video => {
      if(video.title.startsWith(req.body.query.toLowerCase()) || video.uploader.startsWith(req.body.query.toLowerCase())) results.push(video);
    })

    return res.send(results);
  });
})

// Send a comment
app.post("/api/comment", async (req, res) => {
  if (!utils.checkToken(req, "/api/comment")) return res.sendStatus(401);
  
  // Sanity chekcs
  if(!req.body.commenter) return res.sendStatus(400);
  if(!req.body.videoID) return res.sendStatus(400);
  if(!req.body.text) return res.sendStatus(400);
  

  if(req.body.text.trim() == "") return res.sendStatus(400);
  if(!utils.videoExists(req.body.videoID)) return res.sendStatus(404);

  client
    .from("comments")
    .insert({
      commenter: req.body.commenter,
      video_id: req.body.videoID,
      text: req.body.text,
    })
    .then((data) => {
      res.send(data);

      // Discord webhook
      utils.sendWebhook(
        "new comment guys",
        "New COMMENT!!!!!",
        [
          {
            "id": 220464536,
            "description": req.body.text.trim(),
            "fields": [],
            "title": `New comment on video ${req.body.videoID}!`,
            "author": {
              "name": req.body.commenter,
              "url": `http://skibidihub.buttplugstudios.xyz/user/${encodeURIComponent(req.body.commenter)}`
            },
            "url": `http://skibidihub.buttplugstudios.xyz/video/${req.body.videoID}`,
            "color": 917248
          }
        ],
        webhookURL
      )
    });
});

// Like a video
app.post("/api/like/:id", async (req, res) => {
  if(!utils.checkToken(req, "/api/like/:id")) return res.sendStatus(401);
  if(!utils.videoExists(req.params.id)) return res.sendStatus(404);

  const likesData = await client
    .from("videos")
    .select("likes")
    .eq("id", req.params.id);
  
  const likes = likesData["data"][0]["likes"] + 1;

  const video = await client
    .from("videos")
    .update({ likes: likes })
    .eq("id", req.params.id);
  
  res.send(video);
});

// Dislike a video
app.post("/api/dislike/:id", async (req, res) => {
  if(!utils.checkToken(req, "/api/dislike/:id")) return res.sendStatus(401);
  if(!utils.videoExists(req.params.id)) return res.sendStatus(404);

  const dislikesData = await client
    .from("videos")
    .select("dislikes")
    .eq("id", req.params.id);
  
  const dislikes = dislikesData["data"][0]["dislikes"] + 1;

  const video = await client
    .from("videos")
    .update({ dislikes: dislikes })
    .eq("id", req.params.id);
  res.send(video);
});

app.get("/api/userVideos/:id", async (req, res) => {
  const data = await client
    .from("videos")
    .select()
    .eq("uploader", decodeURIComponent(req.params.id));
  res.send(data);
});

app.get("/api/sunset", async (req, res) => {
  const file = fs.readFileSync(path.join(__dirname, "sunset.json"))
  return res.send(JSON.parse(file))
})

app.post("/api/upload", upload.fields([
  { name: 'video' }, { name: 'thumbnail' }
]), utils.multerErrorHandler, async (req, res) => {
  if(!utils.checkBodyVideo(req.body)) return res.status(400).json({ message: "invalid video body" });
  if(!utils.checkToken(req, "/api/upload")) return res.status(401).json({ message: "SIGN IN to UPLOAD videos!!!" });

  await client.from("videos").insert({
    id: req.skibidihub_id,
    likes: 0,
    dislikes: 0,
    description: req.body.description,
    title: req.body.title,
    uploader: req.body.uploader
  }).then(data => {
    res.status(201).json({
      "id": req.skibidihub_id
    })
    // Discord webhook
    utils.sendWebhook(
      `new video guys <@&1274653503448678440> \`\`${req.skibidihub_id}\`\``,
      "New UPLOAD!!!!",
      [
        {
          "id": 220464536,
          "description": req.body.description,
          "fields": [],
          "title": req.body.title,
          "author": {
            "name": req.body.uploader,
            "url": `http://skibidihub.buttplugstudios.xyz/user/${encodeURIComponent(req.body.uploader)}`
          },
          "url": `http://skibidihub.buttplugstudios.xyz/video/${req.skibidihub_id}`,
          "color": 9830655,
          "image": {
            "url": `https://skibidihub.buttplugstudios.xyz/api/webhookThumbnail/${req.skibidihub_id}`
          }
        }
      ],
      webhookURL
    )
  })
})

// Start App
app.listen(port, () => {
  console.log(`skibidihub listening on port ${port}`);
});