<?php

use mystic\forum\Database;
use mystic\forum\exceptions\DatabaseConnectionException;
use mystic\forum\Messaging;
use mystic\forum\orm\Attachment;
use mystic\forum\orm\Post;
use mystic\forum\orm\Topic;
use mystic\forum\orm\TopicLogMessage;
use mystic\forum\orm\User;
use mystic\forum\utils\RequestUtils;

header_remove("X-Powered-By");

const MYSTICBB_VERSION = "0.4.0";

if (($_SERVER["HTTP_USER_AGENT"] ?? "") === "") {
    http_response_code(403);
    exit;
}

define("REGISTRATION_ENABLED", isTrue(env("REGISTRATION_ENABLED") ?? ""));

const __ROOT__ = __DIR__;

session_name("fsid");
session_start();

const MAX_ATTACHMENT_SIZE = 0x200000;
const MAX_ATTACHMENT_COUNT = 4;
const THUMB_MAX_DIM = 100;
const CAPTCHA_PHRASE_LENGTH = 7;
const CAPTCHA_CHARSET = 'ABCDEFGHKLMNPQRTWXYZ234789abdefghkmnpqr';

$_rq_method = $_SERVER["REQUEST_METHOD"] ?? "GET";
$_action = $_GET["_action"] ?? null;

$GLOBALS["action"] = $_action;

function msg_error(string $err, bool $skipLoginCheck = false): void {
    _view("template_start", ["_title" => __("Error")]);
    _view("template_navigation_start");
    if (!$skipLoginCheck)
        _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]);
    _view("template_navigation_end");
    _view("alert_error", ["message" => $err]);
    _view("template_end", [...getThemeAndLangInfo()]);
}

function msg_info(string $msg, bool $skipLoginCheck = false): void {
    _view("template_start", ["_title" => __("Information")]);
    _view("template_navigation_start");
    if (!$skipLoginCheck)
        _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]);
    _view("template_navigation_end");
    _view("alert_info", ["message" => $msg]);
    _view("template_end", [...getThemeAndLangInfo()]);
}

function generateCaptchaText(): string {
    $phrase = "";
    for ($i = 0; $i < CAPTCHA_PHRASE_LENGTH; ++$i)
        $phrase .= CAPTCHA_CHARSET[random_int(0, strlen(CAPTCHA_CHARSET) - 1)];
    return $phrase;
}

function generatePasswordResetLink(Database &$db, User &$user): ?string {
    $token = $db->generateId(20);

    $user->passwordResetToken = $token;
    $user->passwordResetTokenCreated = new \DateTimeImmutable();

    if (!$db->update($user)) {
        return null;
    }

    return env("PUBLIC_URL") . "?_action=pwreset&token=" . urlencode($user->passwordResetToken) . "&sig=" . urlencode(base64_encode(hash("sha256", env("SECRET") . $user->passwordResetToken . $user->id . $user->passwordHash, true)));
}

function decodePasswordResetLink(Database &$db, string $token, string $signature): ?User {
    $user = new User();
    $user->passwordResetToken = $token;

    if (!$db->fetchWhere($user, "password_reset_token")) {
        return null;
    }

    $then = $user->passwordResetTokenCreated;
    $now = new \DateTimeImmutable();
    if (($now->getTimestamp() - $then->getTimestamp()) >= 3600) {
        return null;
    }

    $expectedSignature = base64_encode(hash("sha256", env("SECRET") . $user->passwordResetToken . $user->id . $user->passwordHash, true));
    if ($expectedSignature !== $signature) {
        return null;
    }

    return $user;
}

function getThemeAndLangInfo(): array {
    $availableThemes = [];
    $currentTheme = $_GET["theme"] ?? $_COOKIE["theme"] ?? env("MYSTIC_FORUM_THEME") ?? "default";
    foreach (scandir($dir = __DIR__ . '/themes/') as $ent) {
        if ($ent[0] === "." || !is_dir($dir . "/" . $ent) || !is_file($theme_file = $dir . "/" . $ent . "/theme.json"))
            continue;
        $theme_info = json_decode(file_get_contents($theme_file));
        $availableThemes[$ent] = $theme_info;
    }

    $availableLangs = [
        "en" => "English",
    ];
    foreach (i18n_get_available_locales() as $loc) {
        if (isset($availableLangs[$loc]))
            continue;
        $metadata = i18n_metadata($loc);
        $availableLangs[$loc] = $metadata["langName"] ?? $loc;
    }

    return [
        "availableThemes" => $availableThemes,
        "currentTheme" => $currentTheme,

        "availableLangs" => $availableLangs,
        "currentLang" => i18n_get_current_locale(),
    ];
}

function _view(string $___NAME, array $___PARAMS = []): void {
    $___PATH = __DIR__ . "/application/views/" . $___NAME . ".php";
    if (!is_file($___PATH)) {
        echo "<!--!ERROR Failed to include {" . htmlentities($___NAME) . "}-->\n";
        echo "<br><p><strong style=\"color:red !important\">Failed to include <em>" . htmlentities($___NAME) . "</em></strong></p><br>\n";
        echo "<!--/ERROR-->\n";
    } else {
        extract($___PARAMS);
        echo "<!--{" . htmlentities($___NAME) . "}-->\n";
        include $___PATH;
        echo "<!--{/" . htmlentities($___NAME) . "}-->\n";
    }
}

function isTrue(string $str): bool {
    $str = strtolower($str);
    return in_array($str, ["yes","true","y","t","on","enabled","1","?1"]);
}

function reArrayFiles(&$file_post) {
    $file_ary = [];
    $file_count = count($file_post['name']);
    $file_keys = array_keys($file_post);

    for ($i=0; $i<$file_count; $i++) {
        if ($file_post["error"][$i] !== UPLOAD_ERR_OK)
            continue;
        foreach ($file_keys as $key) {
            $file_ary[$i][$key] = $file_post[$key][$i];
        }
    }

    return $file_ary;
}

function renderPost(string $contents): string {
    $contents = preg_replace('~\R~u', "\n", $contents);
    $contents = trim($contents);
    $contents = preg_replace('/\n{3,}/', "\n\n", $contents);
    $contents = htmlentities($contents);
    $contents = nl2br($contents, false);
    $lines = explode("\n", $contents);
    $contents = "";
    $lineBuf = [];
    $inQuote = false;
    foreach ($lines as $ln) {
        if (str_starts_with($ln, "&gt; ")) {
            if (!$inQuote) {
                $contents .= implode("\n", $lineBuf);
                $lineBuf = [];
                $inQuote = true;
            }
            $lineBuf []= substr($ln, 5);
        } else {
            if ($inQuote) {
                $contents .= "<blockquote>\n" . implode("\n", $lineBuf) . "</blockquote>\n";
                $lineBuf = [];
                $inQuote = false;
                if (trim($ln) === "<br>")
                    continue;
            }
            $lineBuf []= $ln;
        }
    }
    if ($inQuote) {
        $contents .= "<blockquote>" . implode("\n", $lineBuf) . "</blockquote>";
    } else {
        $contents .= implode("\n", $lineBuf);
    }

    return $contents;
}

function env(string $key): ?string {
    $val = getenv($key);
    if ($val === false)
        return null;
    return $val;
}

require_once __DIR__ . "/vendor/autoload.php";
require_once __DIR__ . "/application/i18n.php";

if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["lang"])) {
    parse_str($_SERVER["QUERY_STRING"], $query);
    if (empty($query["lang"])) {
        setcookie("lang", "", 100);
    } else {
        setcookie("lang", $query["lang"], time()+60*60*24*30);
    }
    unset($query["lang"]);
    $path = strtok($_SERVER["REQUEST_URI"], "?");
    header("Location: $path?" . http_build_query($query));
    exit;
}

$user_locale = env("LOCALE") ?? $_COOKIE["lang"] ?? locale_accept_from_http($_SERVER["HTTP_ACCEPT_LANGUAGE"] ?? "");
$chosen_locale = locale_lookup(i18n_get_available_locales(), $user_locale, true, "en");
i18n_locale($user_locale);

$db = null;
try {
    $db = new Database(Database::getConnectionString("db", getenv("POSTGRES_USER"), getenv("POSTGRES_PASSWORD"), getenv("POSTGRES_DBNAME")));
} catch (DatabaseConnectionException $ex) {
    msg_error(
        __("Failed to connect to database:\n%details%", [
            "details" => $ex->getMessage(),
        ]),
        true
    );
    exit;
}

$GLOBALS["db"] = &$db;

$db->ensureTable(User::class);
$db->ensureTable(Topic::class);
$db->ensureTable(Post::class);
$db->ensureTable(Attachment::class);
$db->ensureTable(TopicLogMessage::class);

$superuser = new User();
$superuser->id = "SUPERUSER";
if (!$db->fetch($superuser)) {
    $superUserPassword = base64_encode(random_bytes(12));

    $suEmail = env("MYSTIC_FORUM_SUPERUSER_EMAIL");
    if ($suEmail === null) {
        http_response_code(500);
        Messaging::error("No superuser email defined. Please set the 'MYSTIC_FORUM_SUPERUSER_EMAIL' environment variable accordingly");
        exit;
    }

    $superuser->name = "superuser";
    $superuser->email = $suEmail;
    $superuser->passwordHash = password_hash($superUserPassword, PASSWORD_DEFAULT);
    $superuser->displayName = "SuperUser";
    $superuser->created = new \DateTimeImmutable();
    $superuser->permissionMask = PHP_INT_MAX;
    $superuser->passwordResetRequired = false;
    $superuser->activated = true;
    $superuser->activationToken = "";

    $db->insert($superuser);

    Messaging::info([
        Messaging::bold("Superuser account created"),
        [
            "Username" => $superuser->name,
            "Password" => $superUserPassword,
        ],
        "Please note that the password can only be shown this time, so please note it down!",
    ]);
    exit;
}

$currentUser = RequestUtils::getAuthorizedUser($db);
$GLOBALS["currentUser"] = &$currentUser;

// initialization finished

function invalid_action(string $_action): never {
    http_response_code(404);
    msg_error(__("Invalid or unknown action $_action"));
    exit;
}

if ($_action !== null && !preg_match('/^[a-z][a-z0-9_]*$/', $_action))
    invalid_action($_action);

$_action ??= "_default";

$actionDir = __DIR__ . "/application/actions/$_action";

if (!is_dir($actionDir))
    invalid_action($_action);

$actionMethod = strtolower(preg_replace('/[^A-Za-z]/', '', $_rq_method));

if (is_file($commonFile = $actionDir . "/_common.php"))
    include $commonFile;

if (is_file($anyFile = $actionDir . "/_any.php"))
    include $anyFile;
elseif (is_file($methodFile = $actionDir . "/$actionMethod.php"))
    include $methodFile;
else {
    http_response_code(405);
    Messaging::error("Invalid request method " . RequestUtils::getRequestMethod());
}