const router = require("express").Router();
const RL = require("express-rate-limit");
const safeStorage = require("assistants-safe-storage");

const DiscordOauth2 = require("discord-oauth2");
const oauth = new DiscordOauth2();

module.exports = (app, config, themeConfig) => {
	const storage = new safeStorage(config.bot.config.token);
	const scopes = config.guildAfterAuthorization?.use ? ["identify", "guilds", "guilds.join"] : ["identify", "guilds"];

	const RateLimits = config.rateLimits || {};
	const RateFunctions = {};

	const NoRL = (req, res, next) => next();

	if (RateLimits.discordOAuth2) {
		RateFunctions.discordOAuth2 = RL.rateLimit({
			windowMs: RateLimits.discordOAuth2.windowMs,
			max: RateLimits.discordOAuth2.max,
			message: RateLimits.discordOAuth2.message,
			store: RateLimits.discordOAuth2.store || new RL.MemoryStore(),
		});
	}

	router.get("/", (req, res) => {
		const clientId = req.client.id;
		const redirectUri = req.redirectUri;

		let newPage = "/";
		if (themeConfig.landingPage?.enabled) newPage = "/dash";

		req.session.r = req.query.r || newPage;

		const authorizeUrl = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${scopes.join("%20")}`;

		res.redirect(authorizeUrl);
	});

	router.get("/status", async (req, res) => {
		res.send(req.session?.discordAuthStatus);
	});

	router.get("/callback", RateFunctions.discordOAuth2 ? RateFunctions.discordOAuth2 : NoRL, async (req, res) => {
		req.session.discordAuthStatus = {
			loading: true,
			success: null,
			state: {
				error: null,
				data: null,
			},
		};

		const clientId = req.client.id;
		const clientSecret = req.client.secret;
		const redirectUri = req.redirectUri;

		const accessCode = req.query.code;
		if (!accessCode) return res.redirect("/?error=NoAccessCodeReturnedFromDiscord");

		res.redirect("/loading");

		let OAuth2Response;
		let OAuth2UserResponse;
		let OAuth2GuildsResponse;

		try {
			req.session.discordAuthStatus = {
				loading: true,
				success: null,
				state: {
					error: null,
					data: "Requesting token...",
				},
			};

			req.session.save(function () {});

			OAuth2Response = await oauth.tokenRequest({
				clientId,
				clientSecret,

				code: accessCode,
				scope: scopes.join(" "),
				grantType: "authorization_code",

				redirectUri,
			});
		} catch (err) {
			console.log("Discord.js Route - OAuth2Response (line 88)\n" + err);

			req.session.discordAuthStatus = {
				loading: false,
				success: false,
				state: {
					error: err,
					data: null,
				},
			};

			req.session.save(function () {});

			return;
		}

		/*
			Get User from Discord OAuth2 API using gained access_token and update its values with tag and avatarURL
		*/
		try {
			req.session.discordAuthStatus = {
				loading: true,
				success: null,
				state: {
					error: null,
					data: "Getting User...",
				},
			};

			req.session.save(function () {});

			OAuth2UserResponse = await oauth.getUser(OAuth2Response.access_token);
		} catch (err) {
			console.log("Discord.js Route - OAuth2UserResponse (line 123)\n" + err);

			req.session.discordAuthStatus = {
				loading: false,
				success: false,
				state: {
					error: err,
					data: null,
				},
			};

			req.session.save(function () {});

			return;
		}
		OAuth2UserResponse.tag = `${OAuth2UserResponse.username}#${OAuth2UserResponse.discriminator}`;
		OAuth2UserResponse.avatarURL = OAuth2UserResponse.avatar ? `https://cdn.discordapp.com/avatars/${OAuth2UserResponse.id}/${OAuth2UserResponse.avatar}.png?size=1024` : null;

		/*
			Save user token in Assistants Secure Storage
		*/
		try {
			storage.SaveUser(OAuth2UserResponse.id, OAuth2Response.access_token);
		} catch (err) {
			console.log("Discord.js Route - Assistants Secure Storage (line 147)\n" + err);

			req.session.discordAuthStatus = {
				loading: false,
				success: false,
				state: {
					error: err,
					data: null,
				},
			};

			req.session.save(function () {});

			return;
		}

		/*
			Save user in session
		*/
		req.session.user = OAuth2UserResponse;
		req.session.loggedInLastTime = true;

		/*
			Register user to DBD Stats and emit userLoggedIn event
		*/
		try {
			req.DBDEvents.emit("userLoggedIn", OAuth2UserResponse);
		} catch (err) {
			console.log("Discord.js Route - DBDStats register and DBDEvent emit userLoggedIn (line 173)\n" + err);

			req.session.discordAuthStatus = {
				loading: false,
				success: false,
				state: {
					error: err,
					data: null,
				},
			};
			req.session.save(function () {});

			return;
		}

		/*
			Gain and update session with user guilds
		*/
		try {
			req.session.discordAuthStatus = {
				loading: true,
				success: null,
				state: {
					error: null,
					data: "Getting List of User Guilds...",
				},
			};

			req.session.save(function () {});

			OAuth2GuildsResponse = await oauth.getUserGuilds(OAuth2Response.access_token);
		} catch (err) {
			req.config.reportError("Discord.js Route - OAuth2GuildsResponse (line 207)\n" + err);

			req.session.discordAuthStatus = {
				loading: false,
				success: false,
				state: {
					error: err,
					data: null,
				},
			};

			req.session.save(function () {});

			return;
		}
		req.session.guilds = OAuth2GuildsResponse || [];

		/*
			Loop and fetch each guild into bots cache
		*/
		if (!req.config.disableResolvingGuildCache) {
			try {
				req.session.discordAuthStatus = {
					loading: true,
					success: null,
					state: {
						error: null,
						data: "Resolving guilds cache...",
					},
				};

				req.session.save(function () {});

				for (const g of OAuth2GuildsResponse) {
					try {
						await req.bot.guilds.fetch(g.id);
					} catch (e) { /* ... */ }
				}
			} catch (err) {
				console.log("Discord.js Route - OAuth2GuildsResponse Whole Loop (line 244)" + err);

				req.session.discordAuthStatus = {
					loading: false,
					success: false,
					state: {
						error: err,
						data: null,
					},
				};

				req.session.save(function () {});

				return;
			}
		}

		/*
			If joining specific guild after authorization is enabled, do it
		*/

		if (req.guildAfterAuthorization.use == true) {
			req.session.discordAuthStatus = {
				loading: true,
				success: null,
				state: {
					error: null,
					data: "Authorizing user with guild...",
				},
			};

			req.session.save(function () {});

			try {
				await oauth.addMember({
					accessToken: OAuth2Response.access_token,
					botToken: req.botToken,
					guildId: req.guildAfterAuthorization.guildId,
					userId: OAuth2UserResponse.id,

					...(req.guildAfterAuthorization.options || {}),
					/*
					options?: {
					    nickname?: string,
					    roles?: [string],
					    mute?: boolean,
					    deaf?: boolean,
					}
					*/
				});
			} catch (err) {
				req.config.reportError("Discord.js Route - guildAfterAuthorization (line 297)" + err);
			}
		}

		req.session.discordAuthStatus = {
			loading: false,
			success: true,
			state: {
				error: null,
				data: null,
			},
		};

		req.session.save(function () {});

		return;
	});

	router.get("/logout", (req, res) => {
		const r = req.query.r || "/";
		req.session.destroy();
		res.redirect(r);
	});

	router.get("/guilds/reload", async (req, res) => {
		if (!req.session.user) return res.redirect("/discord");

		/*
			Fetch user token
		*/
		const access_token = storage.GetUser(req.session.user.id);

		if (!access_token)
			return res.send({
				error: true,
				message: "You don't have any access_token saved.",
				login_again_text: true,
			});

		/*
			Gain and update session with user guilds
		*/
		let OAuth2GuildsResponse;

		try {
			OAuth2GuildsResponse = await oauth.getUserGuilds(access_token);
		} catch (err) {
			req.config.reportError("Discord.js Route - OAuth2GuildsResponse for ReloadGuilds (line 344)" + err);

			return res.send({
				error: true,
				message: "An error occured. Access_token is wrong or you're being rate limited.",
				login_again_text: true,
			});
		}

		req.session.guilds = OAuth2GuildsResponse || [];

		/*
			Loop and fetch each guild into bots cache
		*/
		try {
			const Promises = [];

			for (const g of OAuth2GuildsResponse) {
				Promises.push(
					// eslint-disable-next-line no-unused-vars, no-async-promise-executor
					new Promise(async (resolve, reject) => {
						try {
							await req.bot.guilds.fetch(g.id);
						} catch (e) { /* ... */ }

						resolve(1);
					}),
				);
				try {
					await Promises.all(Promises);
				} catch (e) { /* ... */ }
			}
		} catch (err) {
			console.log("Discord.js Route - OAuth2GuildsResponse Whole Loop for ReloadGuilds (line 375)" + err);

			return res.send({
				error: true,
				message: "An error occured. Access_token is wrong or you're being rate limited.",
				login_again_text: true,
			});
		}

		/*
			Success
		*/
		return res.send({
			error: false,
			message: null,
			login_again_text: false,
		});
	});

	return router;
};