import config from "config";
import cors from "cors";
import cookieParser from "cookie-parser";
import bodyParser from "body-parser";
import express from "express";
import http from "http";

import CoreClass from "../core";

let AppModule;
let UsersModule;

class _AppModule extends CoreClass {
	// eslint-disable-next-line require-jsdoc
	constructor() {
		super("app");

		AppModule = this;
	}

	/**
	 * Initialises the app module
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async initialize() {
		UsersModule = this.moduleManager.modules.users;

		const app = (this.app = express());
		const SIDname = config.get("cookie");
		this.server = http.createServer(app).listen(config.get("port"));

		app.use(cookieParser());

		app.use(bodyParser.json());
		app.use(bodyParser.urlencoded({ extended: true }));

		const appUrl = `${config.get("url.secure") ? "https" : "http"}://${config.get("url.host")}`;

		const corsOptions = JSON.parse(JSON.stringify(config.get("cors")));
		corsOptions.origin.push(appUrl);
		corsOptions.credentials = true;

		app.use(cors(corsOptions));
		app.options("*", cors(corsOptions));

		/**
		 * @param {object} res - response object from Express
		 * @param {string} err - custom error message
		 */
		function redirectOnErr(res, err) {
			res.redirect(`${appUrl}?err=${encodeURIComponent(err)}`);
		}

		if (config.get("apis.oidc.enabled")) {
			app.get("/auth/oidc/authorize", async (req, res) => {
				if (this.getStatus() !== "READY") {
					this.log(
						"INFO",
						"APP_REJECTED_OIDC_AUTHORIZE",
						`A user tried to use OIDC authorize, but the APP module is currently not ready.`
					);
					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
				}

				const params = [
					`client_id=${config.get("apis.oidc.client_id")}`,
					`redirect_uri=${UsersModule.oidcRedirectUri}`,
					`scope=basic openid`,
					`response_type=code`
				].join("&");
				return res.redirect(`${UsersModule.oidcAuthorizationEndpoint}?${params}`);
			});

			app.get("/auth/oidc/authorize/callback", async (req, res) => {
				if (this.getStatus() !== "READY") {
					this.log(
						"INFO",
						"APP_REJECTED_OIDC_AUTHORIZE",
						`A user tried to use OIDC authorize, but the APP module is currently not ready.`
					);

					redirectOnErr(res, "Something went wrong on our end. Please try again later.");
					return;
				}

				const { code, state, error, error_description: errorDescription } = req.query;

				// OIDC_AUTHORIZE_CALLBACK job handles login/register
				UsersModule.runJob("OIDC_AUTHORIZE_CALLBACK", { code, state, error, errorDescription })
					.then(({ redirectUrl, sessionId, userId }) => {
						if (sessionId) {
							const date = new Date();
							date.setTime(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000);

							res.cookie(SIDname, sessionId, {
								expires: date,
								secure: config.get("url.secure"),
								path: "/",
								domain: config.get("url.host")
							});

							this.log(
								"INFO",
								"AUTH_OIDC_AUTHORIZE_CALLBACK",
								`User "${userId}" successfully authorized with OIDC.`
							);
						}

						res.redirect(redirectUrl);
					})
					.catch(err => {
						this.log(
							"ERROR",
							"AUTH_OIDC_AUTHORIZE_CALLBACK",
							`Failed to authorize with OIDC. "${err.message}"`
						);

						return redirectOnErr(res, err.message);
					});
			});
		}

		app.get("/auth/verify_email", (req, res) => {
			if (this.getStatus() !== "READY") {
				this.log(
					"INFO",
					"APP_REJECTED_VERIFY_EMAIL",
					`A user tried to use verify email, but the APP module is currently not ready.`
				);
				redirectOnErr(res, "Something went wrong on our end. Please try again later.");
				return;
			}

			const { code } = req.query;

			UsersModule.runJob("VERIFY_EMAIL", { code })
				.then(() => {
					this.log("INFO", "VERIFY_EMAIL", `Successfully verified email.`);

					res.redirect(`${appUrl}?toast=Thank you for verifying your email`);
				})
				.catch(err => {
					this.log("ERROR", "VERIFY_EMAIL", `Verifying email failed. "${err.message}"`);

					res.json({
						status: "error",
						message: err.message
					});
				});
		});
	}

	/**
	 * Returns the express server
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	SERVER() {
		return new Promise(resolve => {
			resolve(AppModule.server);
		});
	}

	/**
	 * Returns the app object
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	GET_APP() {
		return new Promise(resolve => {
			resolve({ app: AppModule.app });
		});
	}

	// EXAMPLE_JOB() {
	// 	return new Promise((resolve, reject) => {
	// 		if (true) resolve({});
	// 		else reject(new Error("Nothing changed."));
	// 	});
	// }
}

export default new _AppModule();