/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/

import { createServer, IncomingMessage, ServerResponse } from "http";
import { SRecord } from "./types";
import { join } from "path";
import { readFileSync } from "fs";
import { TypedEmitter } from "tiny-typed-emitter";
import { betterFetch, log } from "./utils";

const basePath = join(__dirname, "../../res/sachy");
const sessions: SRecord<Session> = {};

const indexStranka = readFileSync(`${basePath}/index.html`).toString();
const tahStranka = readFileSync(`${basePath}/tah.html`).toString();
const promoStranka = readFileSync(`${basePath}/promotion.html`).toString();
const chybaStranka = readFileSync(`${basePath}/error.html`).toString();
const hraStranka = readFileSync(`${basePath}/game.html`).toString();

export const sachyDomena = process.env.sachyDomena ?? "šach.ml";

interface Eventy {
	tah: (playerId: string, square: string, verify: (err?: string) => void, promotionType?: string) => void;
	render: (playerId: string, send: (html: string, err?: true) => void) => void;
}

export const emiter = new TypedEmitter<Eventy>();

function parseCookies(cookies?: string) {
	const list: SRecord<string> = {};
	if (!cookies) return list;

	cookies.split(`;`).forEach(function (cookie) {
		const [name, ...rest] = cookie.split(`=`);
		const nazev = name?.trim();
		if (!nazev) return;
		const value = rest.join(`=`).trim();
		if (!value) return;
		list[nazev] = decodeURIComponent(value);
	});

	return list;
}

function generateId() {
	while (true) {
		const id = Math.round(Math.random() * 3554597877099506 + 101560562963469).toString(36);
		if (!Object.keys(sessions).includes(id)) return id;
	}
}

function startSession(req: IncomingMessage, res: ServerResponse): Session {

	const cookies = parseCookies(req.headers.cookie);
	const sessionId = cookies.sachySession;

	// Jestli on něco pošle a my to máme taky, akorát updateneme dýlku uložení
	if (sessionId && sessions[sessionId]) {
		sessions[sessionId].lastInteractedAt = Date.now();

		res.setHeader("Set-Cookie", `sachySession=${sessionId}; Max-Age=3600`);
		return sessions[sessionId];
	}

	const randomId = generateId();
	sessions[randomId] = { logedIn: false, lastInteractedAt: Date.now() };

	res.setHeader("Set-Cookie", `sachySession=${randomId}; Max-Age=3600`);

	return sessions[randomId];
}

// Session garbage-collection
setInterval(() => {
	for (const key in sessions) {
		if ((sessions[key].lastInteractedAt + 36e5) > Date.now()) return;

		delete sessions[key];
	}
}, 3e5);

function redirect(res: ServerResponse, path: string) {
	res.statusCode = 302;
	res.setHeader("Location", path);
	res.end("302 gone");
}

function login(session: Session, res: ServerResponse) {
	if (session.logedIn) return "uzjsi prihlasenej";

	redirect(res, `https://discord.com/api/oauth2/authorize?client_id=1064269692165963826&redirect_uri=http%3A%2F%2F${encodeURIComponent(sachyDomena)}%2Foauth&response_type=code&scope=identify`);
}

function logout(session: Session, res: ServerResponse) {
	session.logedIn = false;
	redirect(res, "/");
}

function loginErr(res: ServerResponse) {
	res.end(
		chybaStranka
			.replace("$domena", sachyDomena)
			.replace("$error", "Prihlaseni se nezdarilo, zkus to znovu a jestli to nefunguje furt, tak si holt stezuj no")
	);
}

async function overeni(url: URL, session: Session, res: ServerResponse) {

	const code = url.searchParams.get("code");
	const data = await betterFetch("https://discord.com/api/v10/oauth2/token", { method: "post", body: `client_id=1064269692165963826&client_secret=8HHKODmb1HTLWoJGVyoinSU_9323dEla&grant_type=authorization_code&code=${code}&redirect_uri=http%3A%2F%2F${encodeURIComponent(sachyDomena)}%2Foauth`, headers: { "Content-type": "application/x-www-form-urlencoded" } })
		.then(r => r.json())
		.catch(err => log("sachy jednotka:", err));

	if (!data?.access_token) return loginErr(res);

	const uzivatel = await betterFetch("https://discord.com/api/v10/users/@me", { headers: { authorization: `Bearer ${data.access_token}` } })
		.then(r => r.json())
		.catch(err => log("sachy dva:", err));

	if (!uzivatel) return loginErr(res);

	session.logedIn = true;
	session.discordId = uzivatel.id;
	session.discordName = uzivatel.username;

	redirect(res, session.redirect || "/");
}

function validateMove(res: ServerResponse, session: Session, url: string) {

	if (!session.logedIn) return redirect(res, `/login?redirect=${url}`);

	const pismenko = url[1];
	const promoNa: string | undefined = url[3];

	function overeni(err?: string) {
		if (err == "--promote--")
			return res.end(promoStranka.replace("$domena", sachyDomena));

		const response = tahStranka
			.replace("$domena", sachyDomena)
			.replace("$title", err ? "Neplatej tah" : "Úspšný tah")
			.replace("$content", err ?? "Kvalitní tah!")
			.replace("$podminka", err ? "0" : "1");

		res.end(response);
	}

	emiter.emit("tah", session.discordId!, pismenko, overeni, promoNa);
}

function renderHra(url: URL, session: Session, res: ServerResponse) {
	if (!session.logedIn) return redirect(res, `/login?redirect=/play${url.search}`);

	let error: string | undefined = undefined;

	function serveHTML(html: string, err?: true) {
		const vec = hraStranka
			.replace("$domena", sachyDomena);

		let errForPrint = error ?? err;
		if (errForPrint) return res.end(vec.replace("$pecko", html).replace("$hra", ""));

		res.end(vec.replace("$hra", html).replace("$pecko", ""));
	}

	const tah = url.pathname.match(/\/play\/(.)$/);
	if (!tah) return emiter.emit("render", session.discordId!, serveHTML);
	const promoNa = url.search[1];

	function handlePromote(err?: string) {
		if (err == "--promote--")
			return res.end(promoStranka.replace("$domena", sachyDomena));

		error = err;
		emiter.emit("render", session.discordId!, serveHTML);
	}
	emiter.emit("tah", session.discordId!, tah[1], handlePromote, promoNa);

}

const server = createServer((req, res) => {

	const session = startSession(req, res);
	const url = new URL(req.url!, `http://${sachyDomena}`);

	// log(url.pathname, url.search, url.searchParams, req.url);

	// Zpracování tahu
	if (url.pathname.length == 2 || url.pathname.length == 4) return validateMove(res, session, req.url!);

	// Hra v prohlížeči
	if (url.pathname.startsWith("/play")) return renderHra(url, session, res);

	// Jinak
	switch (url.pathname) {
		case "/": {
			const response = indexStranka
				.replaceAll("$domena", sachyDomena)
				.replace("$pecko", session.logedIn ? `Jsi přihlášen jako ${session.discordName}. Pro odhlášení pokračuj <a href="logout">sem</a>.` : `Pro přihlášení pokračuj <a href="login">sem</a>.`);
			return res.end(response);
		}

		case "/login": {
			const path = url.searchParams.get("redirect");
			session.redirect = path || "";
			return login(session, res);
		}

		case "/oauth":
			return overeni(url, session, res);

		case "/logout":
			return logout(session, res);

		default:
			redirect(res, "/");
	}
});

server.listen(50236);

type Session = {
	logedIn: boolean;
	discordId?: string;
	discordName?: string;
	lastInteractedAt: number;
	redirect?: string;
};
