<?php
namespace Misuzu;

use Index\Autoloader;
use Index\Environment;
use Index\Data\ConnectionFailedException;
use Index\Data\DbTools;
use Misuzu\Config\IConfig;
use Misuzu\Config\DbConfig;
use Misuzu\Net\IPAddress;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserSession;
use Misuzu\Users\UserSessionNotFoundException;

define('MSZ_STARTUP', microtime(true));
define('MSZ_ROOT', __DIR__);
define('MSZ_CLI', PHP_SAPI === 'cli');
define('MSZ_DEBUG', is_file(MSZ_ROOT . '/.debug'));
define('MSZ_PUBLIC', MSZ_ROOT . '/public');
define('MSZ_SOURCE', MSZ_ROOT . '/src');
define('MSZ_LIBRARIES', MSZ_ROOT . '/lib');
define('MSZ_CONFIG', MSZ_ROOT . '/config');
define('MSZ_TEMPLATES', MSZ_ROOT . '/templates');
define('MSZ_MIGRATIONS', MSZ_ROOT . '/database');

define('MSZ_NDX_PATH', MSZ_LIBRARIES . '/index');
define('MSZ_NDX_PATH_DEV', MSZ_LIBRARIES . '/index-dev');

require_once (MSZ_DEBUG && is_dir(MSZ_NDX_PATH_DEV) ? MSZ_NDX_PATH_DEV : MSZ_NDX_PATH) . '/index.php';

Autoloader::addNamespace(__NAMESPACE__, MSZ_SOURCE);
Environment::setDebug(MSZ_DEBUG);

mb_internal_encoding('utf-8');
date_default_timezone_set('utc');
set_include_path(get_include_path() . PATH_SEPARATOR . MSZ_ROOT);

set_exception_handler(function(\Throwable $ex) {
    if(MSZ_CLI) {
        echo (string)$ex;
    } else {
        http_response_code(500);
        ob_clean();

        if(MSZ_DEBUG) {
            header('Content-Type: text/plain; charset=utf-8');
            echo (string)$ex;
        } else {
            header('Content-Type: text/html; charset-utf-8');
            echo file_get_contents(MSZ_TEMPLATES . '/500.html');
        }
    }
    exit;
});

require_once 'vendor/autoload.php';

require_once 'utility.php';
require_once 'src/perms.php';
require_once 'src/manage.php';
require_once 'src/url.php';
require_once 'src/Forum/perms.php';
require_once 'src/Forum/forum.php';
require_once 'src/Forum/leaderboard.php';
require_once 'src/Forum/post.php';
require_once 'src/Forum/topic.php';
require_once 'src/Forum/validate.php';

$dbConfig = parse_ini_file(MSZ_CONFIG . '/config.ini', true, INI_SCANNER_TYPED);

if(empty($dbConfig)) {
    echo 'Database config is missing.';
    exit;
}

define('MSZ_DB_INIT', 'SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';');

$db = DbTools::create($dbConfig['dsn']);
$db->execute(MSZ_DB_INIT);

$dbConfig = $dbConfig['Database'] ?? $dbConfig['Database.mysql-main'] ?? [];
DB::init(DB::buildDSN($dbConfig), $dbConfig['username'] ?? '', $dbConfig['password'] ?? '', DB::ATTRS);
DB::exec(MSZ_DB_INIT);

$cfg = new DbConfig($db);
$cfg->reload();

Config::init($cfg);

Mailer::init($cfg->getValue('mail.method', IConfig::T_STR), [
    'host'        => $cfg->getValue('mail.host',           IConfig::T_STR),
    'port'        => $cfg->getValue('mail.port',           IConfig::T_INT, 25),
    'username'    => $cfg->getValue('mail.username',       IConfig::T_STR),
    'password'    => $cfg->getValue('mail.password',       IConfig::T_STR),
    'encryption'  => $cfg->getValue('mail.encryption',     IConfig::T_STR),
    'sender_name' => $cfg->getValue('mail.sender.name',    IConfig::T_STR),
    'sender_addr' => $cfg->getValue('mail.sender.address', IConfig::T_STR),
]);

// replace this with a better storage mechanism
define('MSZ_STORAGE', $cfg->getValue('storage.path', IConfig::T_STR, MSZ_ROOT . '/store'));
if(!is_dir(MSZ_STORAGE))
    mkdir(MSZ_STORAGE, 0775, true);

$msz = new MisuzuContext($db, $cfg);

if(MSZ_CLI) { // Temporary backwards compatibility measure, remove this later
    if(realpath($_SERVER['SCRIPT_FILENAME']) === __FILE__) {
        if(($argv[1] ?? '') === 'cron' && ($argv[2] ?? '') === 'low')
            $argv[2] = '--slow';
        array_shift($argv);
        echo shell_exec(__DIR__ . '/msz ' . implode(' ', $argv));
    }
    return;
}

// Everything below here should eventually be moved to index.php, probably only initialised when required.
// Serving things like the css/js doesn't need to initialise sessions.

ob_start();

if(file_exists(MSZ_ROOT . '/.migrating')) {
    http_response_code(503);
    if(!isset($_GET['_check'])) {
        header('Content-Type: text/html; charset-utf-8');
        echo file_get_contents(MSZ_TEMPLATES . '/503.html');
    }
    exit;
}

if(!is_readable(MSZ_STORAGE) || !is_writable(MSZ_STORAGE)) {
    echo 'Cannot access storage directory.';
    exit;
}

IPAddress::init($msz);

if(!MSZ_DEBUG) {
    $twigCacheDirSfx = GitInfo::hash(true);
    if(empty($twigCacheDirSfx))
        $twigCacheDirSfx = md5(MSZ_ROOT);

    $twigCache = sys_get_temp_dir() . '/msz-tpl-' . $twigCacheDirSfx;
    if(!is_dir($twigCache))
        mkdir($twigCache, 0775, true);
}

Template::init($msz, $twigCache ?? null, MSZ_DEBUG);

Template::set('globals', [
    'site_name' => $cfg->getValue('site.name', IConfig::T_STR, 'Misuzu'),
    'site_description' => $cfg->getValue('site.desc', IConfig::T_STR),
    'site_url' => $cfg->getValue('site.url', IConfig::T_STR),
    'site_twitter' => $cfg->getValue('social.twitter', IConfig::T_STR),
    'site_chat' => $cfg->getValue('sockChat.chatPath.normal', IConfig::T_STR),
]);

Template::addPath(MSZ_TEMPLATES);

if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) {
    $authToken = (new AuthToken)
        ->setUserId(filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT) ?? 0)
        ->setSessionToken(filter_input(INPUT_COOKIE, 'msz_sid') ?? '');

    if($authToken->isValid())
        setcookie('msz_auth', $authToken->pack(), strtotime('1 year'), '/', msz_cookie_domain(), !empty($_SERVER['HTTPS']), true);

    setcookie('msz_uid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true);
    setcookie('msz_sid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true);
}

if(!isset($authToken))
    $authToken = AuthToken::unpack(filter_input(INPUT_COOKIE, 'msz_auth') ?? '');

if($authToken->isValid()) {
    try {
        $sessionInfo = $authToken->getSession();
        if($sessionInfo->hasExpired()) {
            $sessionInfo->delete();
        } elseif($sessionInfo->getUserId() === $authToken->getUserId()) {
            $userInfo = $sessionInfo->getUser();
            if(!$userInfo->isDeleted()) {
                $sessionInfo->setCurrent();
                $userInfo->setCurrent();
                $sessionInfo->bump($_SERVER['REMOTE_ADDR']);

                if($sessionInfo->shouldBumpExpire())
                    setcookie('msz_auth', $authToken->pack(), $sessionInfo->getExpiresTime(), '/', msz_cookie_domain(), !empty($_SERVER['HTTPS']), true);
            }
        }
    } catch(UserNotFoundException $ex) {
        UserSession::unsetCurrent();
        User::unsetCurrent();
    } catch(UserSessionNotFoundException $ex) {
        UserSession::unsetCurrent();
        User::unsetCurrent();
    }

    if(UserSession::hasCurrent()) {
        $userInfo->bumpActivity($_SERVER['REMOTE_ADDR']);
    } else {
        setcookie('msz_auth', '', -9001, '/', msz_cookie_domain(), !empty($_SERVER['HTTPS']), true);
        setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true);
    }
}

CSRF::setGlobalSecretKey($cfg->getValue('csrf.secret', IConfig::T_STR, 'soup'));
CSRF::setGlobalIdentity(
    UserSession::hasCurrent()
        ? UserSession::getCurrent()->getToken()
        : ($_SERVER['REMOTE_ADDR'] ?? '::1')
);

function mszLockdown(): void {
    global $misuzuBypassLockdown, $cfg;

    if($cfg->getValue('private.enabled', IConfig::T_BOOL)) {
        $onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
        $onPasswordPage = parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH) === url('auth-forgot');
        $misuzuBypassLockdown = !empty($misuzuBypassLockdown) || $onLoginPage;

        if(!$misuzuBypassLockdown) {
            if(UserSession::hasCurrent()) {
                $privatePermCat = $cfg->getValue('private.perm.cat', IConfig::T_STR);
                $privatePermVal = $cfg->getValue('private.perm.val', IConfig::T_INT);

                if(!empty($privatePermCat) && $privatePermVal > 0) {
                    if(!perms_check_user($privatePermCat, User::getCurrent()->getId(), $privatePermVal)) {
                        // au revoir
                        UserSession::unsetCurrent();
                        User::unsetCurrent();
                    }
                }
            } elseif(!$onLoginPage && !($onPasswordPage && $cfg->getValue('private.allow_password_reset', IConfig::T_BOOL, true))) {
                url_redirect('auth-login');
                exit;
            }
        }
    }
}

if(parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH) !== '/index.php')
    mszLockdown();

if(!empty($userInfo))
    Template::set('current_user', $userInfo);

$inManageMode = str_starts_with($_SERVER['REQUEST_URI'], '/manage');
$hasManageAccess = User::hasCurrent()
    && !User::getCurrent()->hasActiveWarning()
    && perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_CAN_MANAGE);
Template::set('has_manage_access', $hasManageAccess);

if($inManageMode) {
    if(!$hasManageAccess) {
        echo render_error(403);
        exit;
    }

    Template::set('manage_menu', manage_get_menu(User::getCurrent()->getId()));
}
