const crypto = require("crypto")
const {parse: parseCookie} = require("cookie")
const constants = require("./constants")
const db = require("./db")

function getToken(req, responseHeaders) {
	if (!req.headers.cookie) req.headers.cookie = ""
	const cookie = parseCookie(req.headers.cookie)
	let token = cookie.token
	if (!token) {
		if (responseHeaders) { // we should create a token
			token = setToken(responseHeaders).token
		} else {
			return null
		}
	}
	db.prepare(
		"INSERT INTO SeenTokens (token, seen) VALUES (?, ?)"
			+ " ON CONFLICT (token) DO UPDATE SET seen = excluded.seen"
	).run([token, Date.now()])
	return token
}

function setToken(responseHeaders, token) {
	const setCookie = responseHeaders["set-cookie"] || []
	if (!token) token = crypto.randomBytes(18).toString("base64").replace(/\W/g, "_")
	setCookie.push(`token=${token}; Path=/; Max-Age=2147483647; HttpOnly; SameSite=Lax`)
	responseHeaders["set-cookie"] = setCookie
	return {token, responseHeaders}
}

class User {
	constructor(token) {
		this.token = token
	}

	/** @return {{instance?: string, theme?: integer, save_history?: boolean, local?: boolean, autoHD?: integer, quality?: number, recommended_mode?: integer, dearrow?: integer, dearrow_thumbnail_instance?: string}} */
	getSettings() {
		if (this.token) {
			return db.prepare("SELECT * FROM Settings WHERE token = ?").get(this.token) || {}
		} else {
			return {}
		}
	}

	/** @return {{instance?: string, theme?: integer, save_history?: boolean, local?: boolean, autoHD?: integer, quality?: number, recommended_mode?: integer, dearrow?: integer, dearrow_thumbnail_instance?: string}} */
	getSettingsOrDefaults() {
		const settings = this.getSettings()
		for (const key of Object.keys(constants.user_settings)) {
			if (settings[key] == undefined) settings[key] = constants.user_settings[key].default
		}
		return settings
	}

	getSubscriptions() {
		if (this.token) {
			return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ?").pluck().all(this.token)
		} else {
			return []
		}
	}

	isSubscribed(ucid) {
		if (this.token) {
			return !!db.prepare("SELECT * FROM Subscriptions WHERE token = ? AND ucid = ?").get([this.token, ucid])
		} else {
			return false
		}
	}

	getWatchedVideos() {
		const settings = this.getSettingsOrDefaults()
		if (this.token && settings.save_history) {
			return db.prepare("SELECT videoID FROM WatchedVideos WHERE token = ?").pluck().all(this.token)
		} else {
			return []
		}
	}

	addWatchedVideoMaybe(videoID) {
		const settings = this.getSettingsOrDefaults()
		if (videoID && this.token && settings.save_history) {
			db.prepare("INSERT OR IGNORE INTO WatchedVideos (token, videoID) VALUES (?, ?)").run([this.token, videoID])
		}
	}

	getFilters() {
		if (this.token) {
			return db.prepare("SELECT * FROM Filters WHERE token = ? ORDER BY data ASC").all(this.token)
		} else {
			return []
		}
	}
}

/**
 * @param {any} [responseHeaders] supply this to create a token
 */
function getUser(req, responseHeaders) {
	const token = getToken(req, responseHeaders)
	return new User(token)
}

function generateCSRF() {
	const token = crypto.randomBytes(16).toString("hex")
	const expires = Date.now() + constants.caching.csrf_time
	db.prepare("INSERT INTO CSRFTokens (token, expires) VALUES (?, ?)").run(token, expires)
	return token
}

function checkCSRF(token) {
	const row = db.prepare("SELECT * FROM CSRFTokens WHERE token = ? AND expires > ?").get(token, Date.now())
	if (row) {
		db.prepare("DELETE FROM CSRFTokens WHERE token = ?").run(token)
		return true
	} else {
		return false
	}
}

function cleanCSRF() {
	db.prepare("DELETE FROM CSRFTokens WHERE expires <= ?").run(Date.now())
}
cleanCSRF()
setInterval(cleanCSRF, constants.caching.csrf_time).unref()

module.exports.getToken = getToken
module.exports.setToken = setToken
module.exports.generateCSRF = generateCSRF
module.exports.checkCSRF = checkCSRF
module.exports.getUser = getUser
