const fs = require('fs')
const {render} = require("pinski/plugins")
const path = require("path")
const fetch = require("node-fetch")
const constants = require("../utils/constants")
const {getUser} = require("../utils/getuser")
const db = require("../utils/db")
const cacheManager = require("../eirtubeMods/cache-manager")
const downloader = require("../eirtubeMods/downloader")
const ratelimiting = require("../eirtubeMods/ratelimiting")

function serveFile(path, range, res, req, responseHeaders) {
	responseHeaders.Location = path
	const stats = fs.statSync(path)

	// Download all at once
	if (range == "all")
		range = `bytes=0-${stats.size}`

	if (!range)
		return {
			statusCode: 416,
			contentType: "text/html; charset=UTF-8",
			content: "Wrong range",
			headers: responseHeaders
		}

	// Stolen DIRECTLY from Pinski internals :3
	let ranged = toRange(stats.size, responseHeaders, req)
	responseHeaders["Content-Length"] = ranged.length
	res.writeHead(ranged.statusCode, Object.assign({"Content-Type": "video/mp4"}, responseHeaders))
	if (req.method == "HEAD") return res.end()
	let stream = fs.createReadStream(path, {start: ranged.start, end: ranged.end})
	stream.pipe(res)

	return null
}

// Stolen DIRECTLY from Pinski internals :3
function toRange(length, headers, req) {
	if (length === 0) return {statusCode: 200, start: undefined, end: undefined, length: 0}
	let start = 0
	let end = length-1
	let statusCode = 200
	if (req.headers.range) {
		let match = req.headers.range.match(/^bytes=([0-9]*)-([0-9]*)$/)
		if (match) {
			if (match[1]) {
				let value = +match[1]
				if (!isNaN(value) && value >= start) {
					start = value
					statusCode = 206
				}
			}
			if (match[2]) {
				let value = +match[2]
				if (!isNaN(value) && value <= end) {
					end = value
					statusCode = 206
				}
			}
		}
	}
	if (start > end) {
		start = 0
		end = length-1
		statusCode = 200
	}
	if (statusCode == 206) {
		headers["Accept-Ranges"] = "bytes"
		headers["Content-Range"] = "bytes "+start+"-"+end+"/"+length
	}
	return {statusCode, start, end, length: end - start + 1}
}

module.exports = [
	{
		route: "/getVideo", methods: ["GET", "HEAD"], code: async ({req, url, res}) => {
			const responseHeaders = {
				Location: req.url
			}
			let videoID = url.searchParams.get("v")
			if (videoID) {
				const segs = videoID.split("/")
				videoID = segs[segs.length - 1]
			}
			let quality = url.searchParams.get("q")
			if (quality) {
				const segs = quality.split("/")
				quality = segs[segs.length - 1]
			}

			// Check file exists
			const fname = `${videoID}-${quality}`
			const dlStatus = downloader.downloadStatus(fname)

			// Wait to download
			if (dlStatus == 1)
				await new Promise(resolve => setTimeout(() => {
					if (dlStatus.status > 1)
						resolve()
				}, 1000 * 2))
			// 404
			else if (!downloader.videoExists(fname)) {
				console.log(`dlstatus for ${fname}: ${dlStatus}`)
				return {
					statusCode: 404,
					contentType: "text/html; charset=UTF-8",
					content: "Not found",
					headers: responseHeaders
				}
			}

			return serveFile(path.join(constants.server_setup.video_dl_path, `${fname}.mp4`), url.searchParams.get("dl") == 1 ? "all" : req.headers.range, res, req, responseHeaders)
		}
	},
	{
		route: "/getCaption", methods: ["GET", "HEAD"], code: async ({req, url, res}) => {
			const responseHeaders = {
				Location: req.url
			}
			// NOTE: Default NewLeaf has an oversight where it formats urls with ?lang= instead of ?label= if caption track is not auto-generated.
			// Why? I don't know. It means this won't work. Hope users are using my newleaf where it's fixed (or invidious).
			const remotePath = url.searchParams.get("url")
			const captionExtension = remotePath.split("/api/v1/captions/")[1]
			const captionFilename = captionExtension.split("?label=")[0] + "-" + captionExtension.split("?label=")[1] + ".vvt"
			const out = path.join(constants.server_setup.video_dl_path, captionFilename)

			// Needs download
			const needsDownload = !fs.existsSync(out)
			const redownloading = !needsDownload && url.searchParams.get("redownload") == 1
			if (needsDownload && req.method == "HEAD")
				return render(404, "pug/errors/message-error.pug", { error: new Error("File not found, needs download") }, responseHeaders)
			const checkAuth = ratelimiting.authorize(req, needsDownload || redownloading, responseHeaders)
			if (checkAuth != true)
				return checkAuth

			// Check if file already exists
			if (fs.existsSync(out))
				if (redownloading)
					fs.unlinkSync(out)
				else
					return serveFile(out, req.headers.range || "all", res, req, responseHeaders)

			// Download
			const instanceOrigin = getUser(req).getSettingsOrDefaults().instance
			const fetchURL = new URL(remotePath, instanceOrigin)

			// Force to match caption string
			if (!remotePath.toString().startsWith("/api/v1/captions/"))
				return {
					statusCode: 401,
					content: "EirTube: Unauthorized",
					contentType: "text/plain",
					headers: responseHeaders
				}

			let stream = fs.createWriteStream(out, { flags: "a" })
			let response = await fetch(fetchURL)
			await response.body.pipe(stream, { end: false })
			await new Promise(resolve => response.body.on("end", resolve))
			stream.end()

			return serveFile(out, "all", res, req, responseHeaders)
		}
	},
	{
		route: "/getOgg", methods: ["GET", "HEAD"], code: async ({req, url, res}) => {
			const responseHeaders = {
				Location: req.url
			}

			let videoID = url.searchParams.get("videoID")

			const pathBase = path.join(__dirname, "../")
			const out = path.join(constants.server_setup.ogg_dl_path, `${videoID}.ogg`)

			// Needs download
			const needsDownload = !fs.existsSync(out)
			if (needsDownload && req.method == "HEAD")
				return render(404, "pug/errors/message-error.pug", { error: new Error("File not found, needs extraction") }, responseHeaders)
			const checkAuth = ratelimiting.authorize(req, needsDownload, responseHeaders)
			if (checkAuth != true)
				return checkAuth

			// Check if file already exists
			if (fs.existsSync(out))
				return serveFile(out, "all", res, req, responseHeaders)

			// Get input path
			let pathIn = path.join(constants.server_setup.video_dl_path, `${videoID}.mp4`)
			if (!fs.existsSync(pathIn))
				return render(404, "pug/errors/message-error.pug", { error: new Error("Input video file missing") }, responseHeaders)

			// Check already downloading
			const dlData = cacheManager.read("ogg")[videoID]
			if (dlData != undefined && dlData.status == 1)
				await new Promise(resolve => {
					setTimeout(() => {
						if (dlData.status == 2) // It's 2 for ogg.
							resolve()
					}, 1000)
				})
			else {
				let result = await downloader.createFfmpegOgg(videoID, path.join(pathBase, pathIn), path.join(pathBase, out))
				if (result.error)
					return render(500, "pug/errors/message-error.pug", { error: new Error(result.error) }, responseHeaders)
				// Set timer to delete ogg after a bit
				else
					setTimeout(() => {
						if (fs.existsSync(out))
							fs.unlinkSync(out)
					}, constants.server_setup.time_before_ogg_delete)
			}

			return serveFile(out, "all", res, req, responseHeaders)
		}
	},
	{
		route: "/redownloadVideo", methods: ["GET"], code: async ({req, url}) => {
			const responseHeaders = {
				Location: req.url
			}
			const videoID = url.searchParams.get("videoID")
			const quality = url.searchParams.get("quality")
			let filename = `${videoID}-${quality}`
			let dlData = cacheManager.read("download")[filename]

			// Needs download
			const needsDownload = (dlData == undefined || dlData.status == 3) && downloader.videoExists(filename)
			const checkAuth = ratelimiting.authorize(req, needsDownload, responseHeaders)
			if (checkAuth != true)
				return checkAuth

			// Video is still downloading
			if (dlData != undefined && dlData.status < 3) {
				return {
					statusCode: 500,
					contentType: "application/json",
					content: {
						filename,
						status: "Still downloading!"
					},
					headers: responseHeaders
				}
			// Video doesn't exist
			} else if (!downloader.videoExists(filename)) {
				return {
					statusCode: 500,
					contentType: "application/json",
					content: {
						filename,
						status: "Video doesn't exist."
					},
					headers: responseHeaders
				}
			// Video exists
			} else {
				fs.unlinkSync(path.join(constants.server_setup.video_dl_path, `${filename}.mp4`))
				return {
					statusCode: 307,
					contentType: "text/html; charset=UTF-8",
					content: "Redirecting...",
					headers: {
						...responseHeaders,
						Location: `/watch?v=${videoID}${quality ? `&quality=${quality}` : ""}`
					}
				}
			}
		}
	},
	{
		route: "/cacheInfo", methods: ["GET"], code: async ({req, url}) => {
			const responseHeaders = {
				Location: req.url
			}
			const checkAuth = ratelimiting.authorize(req, false, responseHeaders)
			if (checkAuth != true)
				return checkAuth

			let videoName = url.searchParams.get("videoName") // Includes quality
			let dlData = cacheManager.read("download")[videoName]

			let status = "not found"
			if (videoName && videoName != "" && downloader.videoExists(videoName)) {
				// Case 1: not in memory but exists as a file. probably downloaded on a previous server run
				if (dlData == undefined)
					status = "found"
				// Case 2: in memory
				else if (dlData)
					status = dlData.status < 3 ? "downloading" : "found"
			}

			return {
				statusCode: 200,
				contentType: "application/json",
				content: {
					videoName,
					status
				},
				headers: responseHeaders
			}
		}
	}
]
