"use strict";

import { createServer, IncomingMessage, ServerResponse } from "http";
import { parse as urlParse } from "querystring";
import { readFileSync } from "fs";
import { createHash } from "crypto";
import { Router, route } from "./router.js";
import { getCookies, renderTemplate, redirect, consumePostForm, setCookies } from "./utils.js";
import { User, UserDB } from "./userdb.js";

const udb = new UserDB();
const hasher = (hashable) => createHash("sha256").update(hashable).digest("hex");

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const mainPage = function (req, res, path, params) {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.write(
        `Hello on ${path}!\n You can go on: <br>` +
        `1. Parameters display (in JSON): <a href="/sparamsAsJson?hello=i&am=JSON">*click*</a><br>` +
        `2. Parameters display (in TEXT): <a href="/sparamsAsText?why=yes&i=am&a=plain+text">*click*</a><br>` +
        `3. See some pretty cats: <a href="/showMeACat">*click*</a><br>` +
        `4. Open dashboard: <a href="/dashboard">*click*</a>`
    );
    res.end();
}

const showParameters = function (type) {
    const contentType = type === "JSON" ? "application/json" : "text/plain";
    const serializer = type === "JSON" ? JSON.stringify : (obj => Array.from(Object.keys(obj)).map(e => `Key: ${e}; Value: ${obj[e]}`).join("\n"));

    /**
     * 
     * @param {IncomingMessage} req 
     * @param {ServerResponse} res 
     * @param {String} path 
     * @param {Object} params 
     */
    return function (req, res, path, params) {
        res.writeHead(200, { "Content-Type": contentType });
        res.write(serializer(params));
        res.end();
    }
}

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const showACat = function (req, res, path, params) {
    res.writeHead(200, { "Content-Type": "image/png" });
    res.write(readFileSync("./Cat_poster_1.png"));
    res.end();
}

const router = new Router();
router.addRoute(route("GET", "/"), mainPage);
router.addRoute(route("GET", "/sparamsAsJson"), showParameters("JSON"));
router.addRoute(route("GET", "/sparamsAsText"), showParameters("TEXT"));
router.addRoute(route("GET", "/showMeACat"), showACat);


/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const userRegisterView = (req, res, path, params) => {
    const cookies = getCookies(req);
    if (cookies["user"]) return redirect(res, "/dashboard");

    return renderTemplate(res, "./templates/register.html");
}

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const userRegister = async function (req, res, path, params) {
    const cookies = getCookies(req);
    if (cookies["user"]) return redirect(res, "/dashboard");

    const response = {
        ok: true,
        message: ""
    };
    try {
        const post = await consumePostForm(req);
        console.log(post);
        const login = post.login;
        const password = post.password;
        const passwordRepeated = post.passwordRepeated;
        const status = udb.getUsersCount() === 0 ? "admin" : "user";
        if (udb.hasUser(login)) {
            throw new Error("User already exists!");
        } else if (password != passwordRepeated) {
            throw new Error("Passwords must match!");
        } else {
            udb.putUser(new User(login, hasher(password), status));
        }
    } catch (e) {
        console.error(e);
        response.ok = false;
        response.message = e.message;
    }

    res.statusCode = 200;
    res.setHeader("Content-Type", "application/json");
    res.write(JSON.stringify(response));
    res.end();
}

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const userLoginView = (req, res, path, params) => {
    const cookies = getCookies(req);
    if (cookies["user"]) return redirect(res, "/dashboard");

    return renderTemplate(res, "./templates/login.html");
}

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const userLogin = async function (req, res, path, params) {
    const cookies = getCookies(req);
    if (cookies["user"]) return redirect(res, "/dashboard");

    let response = {
        ok: true,
        message: ""
    };
    try {
        const post = await consumePostForm(req);
        const login = post.login;
        const password = hasher(post.password);
        if (!udb.hasUser(login)) {
            throw new Error("User not exists!");
        }
        const user = udb.getUser(login);
        if (password !== user.password) {
            throw new Error("Password doesn't match!");
        }
        response["user"] = {
            login,
            status: user.status
        };
    } catch (e) {
        console.error(e);
        response.ok = false;
        response.message = e.message;
    }

    res.statusCode = 200;
    res.setHeader("Content-Type", "application/json");
    if (response.ok) {
        setCookies(res, {
            user: JSON.stringify(response["user"])
        });
    }
    res.write(JSON.stringify(response));
    res.end();
}

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const userDashboardView = function (req, res, path, params) {
    const cookies = getCookies(req);
    if (!cookies["user"]) return redirect(res, "/login");

    const user = JSON.parse(cookies["user"]);
    return renderTemplate(res, "./templates/dashboard.html", [
        ["user.name", user.login],
        ["user.status", user.status],
    ]);
}

/**
 * Sadly, not working(
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 * @param {String} path 
 * @param {Object} params 
 */
const userDashboardLogout = function (req, res, path, params) {
    let cookies = getCookies(req);
    if (cookies["user"]) {
        cookies["user"] = "";
        setCookies(res, cookies);
    }
    
    return redirect(res, "/login");
}

router.addRoute(route("GET", "/register"), userRegisterView);
router.addRoute(route("POST", "/register"), userRegister);
router.addRoute(route("GET", "/login"), userLoginView);
router.addRoute(route("POST", "/login"), userLogin);
router.addRoute(route("GET", "/dashboard"), userDashboardView);
router.addRoute(route("GET", "/dashboard/logout"), userDashboardLogout);

/**
 * 
 * @param {IncomingMessage} req 
 * @param {ServerResponse} res 
 */
const requestDispatcher = function (req, res) {
    const [path, params] = req.url.includes("?") ? req.url.split("?", 2) : [req.url, ""];
    const parsedParams = urlParse(params);
    router.dispatch(route(req.method, path))(req, res, path, parsedParams);
}

createServer(requestDispatcher).listen(9889);
