import createError from "http-errors";
import express from "express";

import compression from "compression";
import path from "path";
import cookieParser from "cookie-parser";
import csurf from "csurf";
import rateLimit from "express-rate-limit";

import session from "express-session";
import FileStore from "session-file-store";

import routerLogin from "./routes/login.js";
import routerDashboard from "./routes/dashboard.js";
import routerApi from "./routes/api.js";
import routerLots from "./routes/lots.js";
import routerMaps from "./routes/maps.js";
import routerLotOccupancies from "./routes/lotOccupancies.js";
import routerWorkOrders from "./routes/workOrders.js";
import routerReports from "./routes/reports.js";
import routerAdmin from "./routes/admin.js";

import * as configFunctions from "./helpers/functions.config.js";
import * as dateTimeFns from "@cityssm/expressjs-server-js/dateTimeFns.js";
import * as stringFns from "@cityssm/expressjs-server-js/stringFns.js";
import * as htmlFns from "@cityssm/expressjs-server-js/htmlFns.js";

import { version } from "./version.js";

import * as databaseInitializer from "./helpers/initializer.database.js";

import debug from "debug";
import { apiGetHandler } from "./handlers/permissions.js";
const debugApp = debug("lot-occupancy-system:app");

/*
 * INITALIZE THE DATABASE
 */

databaseInitializer.initializeDatabase();

/*
 * INITIALIZE APP
 */

const __dirname = ".";

export const app = express();

if (!configFunctions.getProperty("reverseProxy.disableEtag")) {
    app.set("etag", false);
}

// View engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");

if (!configFunctions.getProperty("reverseProxy.disableCompression")) {
    app.use(compression());
}

app.use((request, _response, next) => {
    debugApp(`${request.method} ${request.url}`);
    next();
});

app.use(express.json());

app.use(
    express.urlencoded({
        extended: false
    })
);

app.use(cookieParser());
app.use(
    csurf({
        cookie: true
    })
);

/*
 * Rate Limiter
 */

const limiter = rateLimit({
    windowMs: 1000,
    max: 25 * Math.max(3, configFunctions.getProperty("users.canLogin").length)
});

app.use(limiter);

/*
 * STATIC ROUTES
 */

const urlPrefix = configFunctions.getProperty("reverseProxy.urlPrefix");

if (urlPrefix !== "") {
    debugApp("urlPrefix = " + urlPrefix);
}

app.use(urlPrefix, express.static(path.join("public")));

app.use(
    urlPrefix + "/lib/fa",
    express.static(
        path.join("node_modules", "@fortawesome", "fontawesome-free")
    )
);

app.use(
    urlPrefix + "/lib/cityssm-bulma-webapp-js",
    express.static(path.join("node_modules", "@cityssm", "bulma-webapp-js"))
);

app.use(
    urlPrefix + "/lib/cityssm-bulma-js",
    express.static(path.join("node_modules", "@cityssm", "bulma-js", "dist"))
);

app.use(
    urlPrefix + "/lib/leaflet",
    express.static(path.join("node_modules", "leaflet", "dist"))
);

app.use(
    urlPrefix + "/lib/randomcolor",
    express.static(path.join("node_modules", "randomcolor"))
);

/*
 * SESSION MANAGEMENT
 */

const sessionCookieName: string =
    configFunctions.getProperty("session.cookieName");

const FileStoreSession = FileStore(session);

// Initialize session
app.use(
    session({
        store: new FileStoreSession({
            path: "./data/sessions",
            logFn: debug("general-licence-manager:session"),
            retries: 10
        }),
        name: sessionCookieName,
        secret: configFunctions.getProperty("session.secret"),
        resave: true,
        saveUninitialized: false,
        rolling: true,
        cookie: {
            maxAge: configFunctions.getProperty("session.maxAgeMillis"),
            sameSite: "strict"
        }
    })
);

// Clear cookie if no corresponding session
app.use((request, response, next) => {
    if (request.cookies[sessionCookieName] && !request.session.user) {
        response.clearCookie(sessionCookieName);
    }

    next();
});

// Redirect logged in users
const sessionChecker = (
    request: express.Request,
    response: express.Response,
    next: express.NextFunction
) => {
    if (request.session.user && request.cookies[sessionCookieName]) {
        return next();
    }

    return response.redirect(
        `${urlPrefix}/login?redirect=${request.originalUrl}`
    );
};

/*
 * ROUTES
 */

// Make the user and config objects available to the templates

app.use((request, response, next) => {
    response.locals.buildNumber = version;

    response.locals.user = request.session.user;
    response.locals.csrfToken = request.csrfToken();

    response.locals.configFunctions = configFunctions;
    response.locals.dateTimeFunctions = dateTimeFns;
    response.locals.stringFunctions = stringFns;
    response.locals.htmlFunctions = htmlFns;

    response.locals.urlPrefix = configFunctions.getProperty(
        "reverseProxy.urlPrefix"
    );

    next();
});

app.get(urlPrefix + "/", sessionChecker, (_request, response) => {
    response.redirect(urlPrefix + "/dashboard");
});

app.use(urlPrefix + "/dashboard", sessionChecker, routerDashboard);

app.use(urlPrefix + "/api/:apiKey", apiGetHandler, routerApi);

app.use(urlPrefix + "/lots", sessionChecker, routerLots);
app.use(urlPrefix + "/maps", sessionChecker, routerMaps);
app.use(urlPrefix + "/lotOccupancies", sessionChecker, routerLotOccupancies);
app.use(urlPrefix + "/workOrders", sessionChecker, routerWorkOrders);

app.use(urlPrefix + "/reports", sessionChecker, routerReports);
app.use(urlPrefix + "/admin", sessionChecker, routerAdmin);

app.all(urlPrefix + "/keepAlive", (_request, response) => {
    response.json(true);
});

app.use(urlPrefix + "/login", routerLogin);

app.get(urlPrefix + "/logout", (request, response) => {
    if (request.session.user && request.cookies[sessionCookieName]) {
        // eslint-disable-next-line unicorn/no-null
        request.session.destroy(null);
        request.session = undefined;
        response.clearCookie(sessionCookieName);
        response.redirect(urlPrefix + "/");
    } else {
        response.redirect(urlPrefix + "/login");
    }
});

// Catch 404 and forward to error handler
app.use((_request, _response, next) => {
    next(createError(404));
});

// Error handler
app.use(
    (
        error: {
            status: number;
            message: string;
        },
        request: express.Request,
        response: express.Response
    ) => {
        // Set locals, only providing error in development
        response.locals.message = error.message;
        response.locals.error =
            request.app.get("env") === "development" ? error : {};

        // Render the error page
        response.status(error.status || 500);
        response.render("error");
    }
);

export default app;
