const router = require("express").Router(),
	RL = require("express-rate-limit");

module.exports = (app, config, themeConfig) => {
	const RateLimits = config.rateLimits || {};
	const RateFunctions = {};

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

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

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

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

	router.get("/manage", RateFunctions.manage ? RateFunctions.manage : NoRL, async (req, res) => {
		if (!req.session.user) return res.redirect("/discord?r=/manage");

		let customThemeOptions;
		if (themeConfig?.customThemeOptions?.manage) {
			customThemeOptions = await themeConfig.customThemeOptions.manage({
				req: req,
				res: res,
				config: config,
			});
		}

		res.render("guilds", {
			req: req,
			bot: config.bot,
			themeConfig: req.themeConfig,
			customThemeOptions: customThemeOptions || {},
			config,
		});
	});

	router.get("/guild/:id", RateFunctions.guildPage ? RateFunctions.guildPage : NoRL, async (req, res) => {
		res.redirect("/settings/" + req.params.id);

		if (!req.session.user) return res.redirect("/discord?r=/guild/" + req.params.id);

		let customThemeOptions;
		if (themeConfig?.customThemeOptions?.getGuild) {
			customThemeOptions = await themeConfig.customThemeOptions.getGuild({
				req: req,
				res: res,
				config: config,
				guildId: req.params.id,
			});
		}

		const bot = config.bot;
		if (!bot.guilds.cache.get(req.params.id)) {
			try {
				await bot.guilds.fetch(req.params.id);
			} catch (e) { /* ... */ }
		}

		if (!bot.guilds.cache.get(req.params.id)) return res.redirect("/manage?error=noPermsToManageGuild");
		if (!bot.guilds.cache.get(req.params.id).members.cache.get(req.session.user.id)) {
			try {
				await bot.guilds.cache.get(req.params.id).members.fetch(req.session.user.id);
			} catch (e) { /* ... */ }
		}

		if (!bot.guilds.cache.get(req.params.id).members.cache.get(req.session.user.id).permissions.has(config.requiredPermissions)) return res.redirect("/manage?error=noPermsToManageGuild");

		if (bot.guilds.cache.get(req.params.id).channels.cache.size < 1) {
			try {
				await bot.guilds.cache.get(req.params.id).channels.fetch();
			} catch (e) { /* ... */ }
		}

		if (bot.guilds.cache.get(req.params.id).roles.cache.size < 2) {
			try {
				await bot.guilds.cache.get(req.params.id).roles.fetch();
			} catch (e) { /* ... */ }
		}

		const actual = {};
		const canUseList = {};

		if (!config.useCategorySet)
			for (const s of config.settings) {
				if (!canUseList[s.categoryId]) canUseList[s.categoryId] = {};

				for (const c of s.categoryOptionsList) {
					if (c.allowedCheck) {
						const canUse = await c.allowedCheck({
							guild: { id: req.params.id },
							user: { id: req.session.user.id },
						});

						if (typeof canUse != "object") throw new TypeError(`${s.categoryId} category option with id ${c.optionId} allowedCheck function need to return {allowed: Boolean, errorMessage: String | null}`);

						canUseList[s.categoryId][c.optionId] = canUse;
					} else {
						canUseList[s.categoryId][c.optionId] = {
							allowed: true,
							errorMessage: null,
						};
					}

					if (c.optionType.type === "spacer" || c.optionType.type === "multiRow" || c.optionType.type === "embedBuilder") { /* ... */ } else {
						if (!actual[s.categoryId]) actual[s.categoryId] = {};

						if (!actual[s.categoryId][c.optionId]) {
							actual[s.categoryId][c.optionId] = await c.getActualSet({
								guild: {
									id: req.params.id,
									object: bot.guilds.cache.get(req.params.id),
								},
								user: {
									id: req.session.user.id,
									object: bot.guilds.cache.get(req.params.id).members.cache.get(req.session.user.id),
								},
							});
						}
					}
				}
			}
		else
			for (const category of config.settings) {
				if (!canUseList[category.categoryId]) canUseList[category.categoryId] = {};

				const catGAS = await category.getActualSet({
					guild: {
						id: req.params.id,
						object: bot.guilds.cache.get(req.params.id),
					},
					user: {
						id: req.session.user.id,
						object: bot.guilds.cache.get(req.params.id).members.cache.get(req.session.user.id),
					},
				});

				for (const o of catGAS) {
					if (!o || !o?.optionId) console.log("WARNING: You haven't set the optionId for a category option in your config. This is required for the category option to work.");
					else {
						const option = category.categoryOptionsList.find((c) => c.optionId == o.optionId);

						if (option) {
							if (option.allowedCheck) {
								const canUse = await option.allowedCheck({
									guild: { id: req.params.id },
									user: { id: req.session.user.id },
								});

								if (typeof canUse != "object") throw new TypeError(`${category.categoryId} category option with id ${option.optionId} allowedCheck function need to return {allowed: Boolean, errorMessage: String | null}`);

								canUseList[category.categoryId][option.optionId] = canUse;
							} else {
								canUseList[category.categoryId][option.optionId] = {
									allowed: true,
									errorMessage: null,
								};
							}

							if (option.optionType !== "spacer") {
								if (!actual[category.categoryId]) actual[category.categoryId] = {};

								if (!actual[category.categoryId][option.optionId])
									actual[category.categoryId][option.optionId] = o.data;
							}
						} else
							console.log(`WARNING: Option ${o.optionId} in category ${category.categoryId} doesn't exist in your config.`);
					}
				}
			}

		let errors;
		let success;

		if (req.session.errors)
			if (String(req.session.errors).includes("%is%")) {
				errors = req.session.errors.split("%and%");
			}

		if (req.session.success) {
			if (typeof req.session.success == "boolean") {
				success = true;
			} else {
				if (String(req.session.success).includes("%is%"))
					success = req.session.success.split("%and%");
			}
		}

		req.session.errors = null;
		req.session.success = null;

		res.render("guild", {
			successes: success,
			errors: errors,
			settings: config.settings,
			actual: actual,
			canUseList,
			bot: config.bot,
			req: req,
			guildid: req.params.id,
			themeConfig: req.themeConfig,
			customThemeOptions: customThemeOptions || {},
			config,
		});
	});

	router.post("/settings/update/:guildId/:categoryId", RateFunctions.settingsUpdatePostAPI ? RateFunctions.settingsUpdatePostAPI : NoRL, async (req, res) => {
		if (!req.session.user)
			return res.redirect("/discord?r=/guild/" + req.params.guildId);

		// let customThemeOptions;
		// if (themeConfig?.customThemeOptions?.settingsUpdate) {
		// 	customThemeOptions =
		// 		await themeConfig.customThemeOptions.settingsUpdate({
		// 			req: req,
		// 			config: config,
		// 			guildId: req.params.id,
		// 			categoryId: req.params.categoryId,
		// 		});
		// }

		const bot = config.bot;
		const member = bot.guilds.cache.get(req.params.guildId).members.cache.get(req.session.user.id);

		if (!bot.guilds.cache.get(req.params.guildId)) return res.redirect("/manage?error=noPermsToManageGuild");

		await bot.guilds.cache.get(req.params.guildId).members.fetch(req.session.user.id);

		if (!member.permissions.has(config.requiredPermissions)) return res.redirect("/manage?error=noPermsToManageGuild");

		const cid = req.params.categoryId;
		const settings = config.settings;

		const category = settings.find(c => c.categoryId == cid);

		if (!category)
			return res.send({
				error: true,
				message: "No category found",
			});

		let setNewRes;
		const errors = [];
		let successes = [];
		const catO = [];

		const userGuildMemberObject = bot.guilds.cache.get(req.params.guildId).members.cache.get(req.session.user.id);
		const guildObject = bot.guilds.cache.get(req.params.guildId);

		for (const option of category.categoryOptionsList) {
			let canUse = {};

			if (option.allowedCheck) {
				canUse = await option.allowedCheck({
					guild: { id: req.params.guildId },
					user: { id: req.session.user.id },
				});
			} else {
				canUse = {
					allowed: true,
					errorMessage: null,
				};
			}

			if (canUse.allowed == false) {
				setNewRes = {
					error: canUse.errorMessage,
				};

				errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
			} else if (option.optionType != "spacer") {
				if (config.useCategorySet) {
					if (option.optionType.type == "rolesMultiSelect" || option.optionType.type == "channelsMultiSelect" || option.optionType.type == "multiSelect") {
						if (!req.body[option.optionId] || req.body[option.optionId] == null || req.body[option.optionId] == undefined)
							catO.push({
								optionId: option.optionId,
								data: [],
							});
						else if (typeof req.body[option.optionId] != "object")
							catO.push({
								optionId: option.optionId,
								data: [req.body[option.optionId]],
							});
						else
							catO.push({
								optionId: option.optionId,
								data: req.body[option.optionId],
							});
					} else if (option.optionType.type == "switch") {
						if (req.body[option.optionId] || req.body[option.optionId] == null || req.body[option.optionId] == undefined) {
							if (req.body[option.optionId] == null || req.body[option.optionId] == undefined)
								catO.push({
									optionId: option.optionId,
									data: false,
								});
							else
								catO.push({
									optionId: option.optionId,
									data: true,
								});
						}
					} else if (option.optionType.type == "embedBuilder") {
						if (req.body[option.optionId] == null || req.body[option.optionId] == undefined)
							catO.push({
								optionId: option.optionId,
								data: option.optionType.data,
							});
						else {
							try {
								const parsedResponse = JSON.parse(req.body[option.optionId]);

								catO.push({
									optionId: option.optionId,
									data: parsedResponse,
								});
							} catch (err) {
								catO.push({
									optionId: option.optionId,
									data: option.optionType.data,
								});
							}
						}
					} else {
						if (req.body[option.optionId] == undefined || req.body[option.optionId] == null)
							catO.push({
								optionId: option.optionId,
								data: null,
							});
						else
							catO.push({
								optionId: option.optionId,
								data: req.body[option.optionId],
							});
					}
				} else {
					if (option.optionType.type == "rolesMultiSelect" || option.optionType.type == "channelsMultiSelect" || option.optionType.type == "multiSelect") {
						if (!req.body[option.optionId] || req.body[option.optionId] == null || req.body[option.optionId] == undefined) {
							setNewRes = await option.setNew({
								guild: { id: req.params.guildId, object: guildObject },
								user: { id: req.session.user.id, object: userGuildMemberObject },
								newData: [],
							});

							setNewRes ? null : (setNewRes = {});

							if (setNewRes.error) {
								errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
							} else
								successes.push(option.optionName);
						} else if (typeof req.body[option.optionId] != "object") {
							setNewRes = await option.setNew({
								guild: { id: req.params.guildId, object: guildObject },
								user: { id: req.session.user.id, object: userGuildMemberObject },
								newData: [req.body[option.optionId]],
							});

							setNewRes ? null : (setNewRes = {});

							if (setNewRes.error)
								errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
							else
								successes.push(option.optionName);
						} else {
							setNewRes = await option.setNew({
								guild: { id: req.params.guildId, object: guildObject },
								user: { id: req.session.user.id, object: userGuildMemberObject },
								newData: req.body[option.optionId],
							});

							setNewRes ? null : (setNewRes = {});

							if (setNewRes.error)
								errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
							else
								successes.push(option.optionName);
						}
					} else if (option.optionType.type == "switch") {
						if (req.body[option.optionId] || req.body[option.optionId] == null || req.body[option.optionId] == undefined) {
							if (req.body[option.optionId] == null || req.body[option.optionId] == undefined) {
								setNewRes = (await option.setNew({
									guild: { id: req.params.guildId, object: guildObject },
									user: { id: req.session.user.id, object: userGuildMemberObject },
									newData: false,
								})) || {};

								setNewRes ? null : (setNewRes = {});

								if (setNewRes.error)
									errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
								else
									successes.push(option.optionName);
							} else {
								setNewRes = (await option.setNew({
									guild: { id: req.params.guildId, object: guildObject },
									user: { id: req.session.user.id, object: userGuildMemberObject },
									newData: true,
								})) || {};

								setNewRes ? null : (setNewRes = {});

								if (setNewRes.error)
									errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
								else
									successes.push(option.optionName);
							}
						}
					} else if (option.optionType.type == "embedBuilder") {
						if (req.body[option.optionId] == null || req.body[option.optionId] == undefined) {
							setNewRes = (await option.setNew({
								guild: { id: req.params.guildId, object: guildObject },
								user: { id: req.session.user.id, object: userGuildMemberObject },
								newData: option.optionType.data,
							})) || {};

							setNewRes ? null : (setNewRes = {});

							if (setNewRes.error)
								errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
							else
								successes.push(option.optionName);
						} else {
							try {
								const parsedResponse = JSON.parse(req.body[option.optionId]);

								setNewRes = (await option.setNew({
									guild: { id: req.params.guildId, object: guildObject },
									user: { id: req.session.user.id, object: userGuildMemberObject },
									newData: parsedResponse,
								})) || {};

								setNewRes ? null : (setNewRes = {});

								if (setNewRes.error)
									errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
								else
									successes.push(option.optionName);
							} catch (err) {
								setNewRes = (await option.setNew({
									guild: { id: req.params.guildId, object: guildObject },
									user: { id: req.session.user.id, object: userGuildMemberObject },
									newData: option.optionType.data,
								})) || {};

								setNewRes = {
									error: "JSON parse for embed builder went wrong, your settings have been reset.",
								};

								if (setNewRes.error)
									errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
								else
									successes.push(option.optionName);
							}
						}
					} else {
						if (req.body[option.optionId] == undefined || req.body[option.optionId] == null) {
							setNewRes = (await option.setNew({
								guild: { id: req.params.guildId, object: guildObject },
								user: { id: req.session.user.id, object: userGuildMemberObject },
								newData: null,
							})) || {};

							setNewRes ? null : (setNewRes = {});

							if (setNewRes.error)
								errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
							else
								successes.push(option.optionName);
						} else {
							setNewRes = (await option.setNew({
								guild: { id: req.params.guildId, object: guildObject },
								user: { id: req.session.user.id, object: userGuildMemberObject },
								newData: req.body[option.optionId],
							})) || {};

							setNewRes ? null : (setNewRes = {});

							if (setNewRes.error)
								errors.push(option.optionName + "%is%" + setNewRes.error + "%is%" + option.optionId);
							else
								successes.push(option.optionName);
						}
					}
				}
			}
		}

		if (config.useCategorySet && catO.length) {
			let sNR = await category.setNew({
				guild: { id: req.params.guildId, object: guildObject },
				user: { id: req.session.user.id, object: userGuildMemberObject },
				data: catO,
			});

			sNR ? null : (sNR = {});

			if (sNR.error) errors.push(category.categoryId + "%is%" + sNR.error);
			else successes.push(category.categoryId);
		}

		const successesForDBDEvent = [];
		const errorsForDBDEvent = [];

		successes.forEach(item => {
			if (typeof item == "string") successesForDBDEvent.push(item.split("%is%"));
		});

		errors.forEach(item => {
			if (typeof item == "string") errorsForDBDEvent.push(item.split("%is%"));
		});

		req.DBDEvents.emit("guildSettingsUpdated", {
			user: req.session.user,
			changes: {
				successesForDBDEvent,
				errorsForDBDEvent,
			},
			guildId: req.params.guildId,
		});

		if (errors[0]) {
			if (!successes) successes = [];
			req.session.success = successes.join("%and%");
			req.session.errors = errors.join("%and%");

			return res.redirect("/guild/" + req.params.guildId);
		} else {
			req.session.success = true;
			req.session.errors = false;

			return res.redirect("/guild/" + req.params.guildId);
		}
	});

	return router;
};