<?php
namespace Misuzu;

use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigrationRepo;
use Index\Data\Migration\DbMigrationManager;
use Index\Data\Migration\FsDbMigrationRepo;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\Router;
use Misuzu\Template;
use Misuzu\Auth\AuthInfo;
use Misuzu\Auth\AuthTokenPacker;
use Misuzu\Auth\LoginAttempts;
use Misuzu\Auth\RecoveryTokens;
use Misuzu\Auth\Sessions;
use Misuzu\Auth\TwoFactorAuthSessions;
use Misuzu\AuditLog\AuditLog;
use Misuzu\Changelog\Changelog;
use Misuzu\Changelog\ChangelogRoutes;
use Misuzu\Comments\Comments;
use Misuzu\Config\IConfig;
use Misuzu\Counters\Counters;
use Misuzu\Emoticons\Emotes;
use Misuzu\Home\HomeRoutes;
use Misuzu\Info\InfoRoutes;
use Misuzu\News\News;
use Misuzu\News\NewsRoutes;
use Misuzu\Profile\ProfileFields;
use Misuzu\Satori\SatoriRoutes;
use Misuzu\SharpChat\SharpChatRoutes;
use Misuzu\Users\Bans;
use Misuzu\Users\BanInfo;
use Misuzu\Users\ModNotes;
use Misuzu\Users\Roles;
use Misuzu\Users\Users;
use Misuzu\Users\UserInfo;
use Misuzu\Users\Warnings;
use Misuzu\Users\Assets\AssetsRoutes;

// this class should function as the root for everything going forward
// no more magical static classes that are just kind of assumed to exist
// it currently looks Pretty Messy, but most everything else will be holding instances of other classes
// instances of certain classes should only be made as needed,
//  dunno if i want null checks some maybe some kind of init func should be called first like is the case
//  with the http shit
class MisuzuContext {
    private IDbConnection $dbConn;
    private IConfig $config;
    private HttpFx $router;
    private AuditLog $auditLog;
    private Emotes $emotes;
    private Changelog $changelog;
    private News $news;
    private Comments $comments;
    private LoginAttempts $loginAttempts;
    private RecoveryTokens $recoveryTokens;
    private ModNotes $modNotes;
    private Bans $bans;
    private Warnings $warnings;
    private TwoFactorAuthSessions $tfaSessions;
    private Roles $roles;
    private Users $users;
    private Sessions $sessions;
    private Counters $counters;
    private ProfileFields $profileFields;
    private AuthInfo $authInfo;

    public function __construct(IDbConnection $dbConn, IConfig $config) {
        $this->dbConn = $dbConn;
        $this->config = $config;
        $this->auditLog = new AuditLog($this->dbConn);
        $this->emotes = new Emotes($this->dbConn);
        $this->changelog = new Changelog($this->dbConn);
        $this->news = new News($this->dbConn);
        $this->comments = new Comments($this->dbConn);
        $this->loginAttempts = new LoginAttempts($this->dbConn);
        $this->recoveryTokens = new RecoveryTokens($this->dbConn);
        $this->modNotes = new ModNotes($this->dbConn);
        $this->bans = new Bans($this->dbConn);
        $this->warnings = new Warnings($this->dbConn);
        $this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
        $this->roles = new Roles($this->dbConn);
        $this->users = new Users($this->dbConn);
        $this->sessions = new Sessions($this->dbConn);
        $this->counters = new Counters($this->dbConn);
        $this->profileFields = new ProfileFields($this->dbConn);
        $this->authInfo = new AuthInfo;
    }

    public function getDbConn(): IDbConnection {
        return $this->dbConn;
    }

    public function getDbQueryCount(): int {
        $result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"');
        return $result->next() ? $result->getInteger(1) : 0;
    }

    public function createMigrationManager(): DbMigrationManager {
        return new DbMigrationManager($this->dbConn, 'msz_' . DbMigrationManager::DEFAULT_TABLE);
    }

    public function createMigrationRepo(): IDbMigrationRepo {
        return new FsDbMigrationRepo(MSZ_MIGRATIONS);
    }

    public function getConfig(): IConfig {
        return $this->config;
    }

    public function getRouter(): Router {
        return $this->router->getRouter();
    }

    public function getEmotes(): Emotes {
        return $this->emotes;
    }

    public function getChangelog(): Changelog {
        return $this->changelog;
    }

    public function getNews(): News {
        return $this->news;
    }

    public function getComments(): Comments {
        return $this->comments;
    }

    public function getAuditLog(): AuditLog {
        return $this->auditLog;
    }

    public function getLoginAttempts(): LoginAttempts {
        return $this->loginAttempts;
    }

    public function getRecoveryTokens(): RecoveryTokens {
        return $this->recoveryTokens;
    }

    public function getModNotes(): ModNotes {
        return $this->modNotes;
    }

    public function getBans(): Bans {
        return $this->bans;
    }

    public function getWarnings(): Warnings {
        return $this->warnings;
    }

    public function getTFASessions(): TwoFactorAuthSessions {
        return $this->tfaSessions;
    }

    public function getRoles(): Roles {
        return $this->roles;
    }

    public function getUsers(): Users {
        return $this->users;
    }

    public function getSessions(): Sessions {
        return $this->sessions;
    }

    public function getCounters(): Counters {
        return $this->counters;
    }

    public function getProfileFields(): ProfileFields {
        return $this->profileFields;
    }

    public function createAuthTokenPacker(): AuthTokenPacker {
        return new AuthTokenPacker($this->config->getString('auth.secret', 'meow'));
    }

    public function getAuthInfo(): AuthInfo {
        return $this->authInfo;
    }

    // isLoggedIn and getActiveUser are proxied for convenience, supply authInfo to things in the future
    public function isLoggedIn(): bool {
        return $this->authInfo->isLoggedIn();
    }

    public function getActiveUser(): ?UserInfo {
        return $this->authInfo->getUserInfo();
    }

    private array $activeBansCache = [];

    public function tryGetActiveBan(UserInfo|string|null $userInfo = null): ?BanInfo {
        if($userInfo === null) {
            if($this->isLoggedIn())
                $userInfo = $this->getActiveUser();
            else return null;
        }

        $userId = (string)$userInfo->getId();
        if(array_key_exists($userId, $this->activeBansCache))
            return $this->activeBansCache[$userId];

        return $this->activeBansCache[$userId] = $this->bans->tryGetActiveBan($userId);
    }

    public function hasActiveBan(UserInfo|string|null $userInfo = null): bool {
        return $this->tryGetActiveBan($userInfo) !== null;
    }

    public function createAuditLog(string $action, array $params = [], UserInfo|string|null $userInfo = null): void {
        if($userInfo === null && $this->isLoggedIn())
            $userInfo = $this->getActiveUser();

        $this->auditLog->createLog(
            $userInfo,
            $action,
            $params,
            $_SERVER['REMOTE_ADDR'] ?? '::1',
            $_SERVER['COUNTRY_CODE'] ?? 'XX'
        );
    }

    public function getHeaderMenu(?UserInfo $userInfo): array {
        $hasUserInfo = $userInfo?->isDeleted() === false;
        $menu = [];

        $home = [
            'title' => 'Home',
            'url' => url('index'),
            'menu' => [],
        ];

        if($hasUserInfo)
            $home['menu'][] = [
                'title' => 'Members',
                'url' => url('user-list'),
            ];

        $home['menu'][] = [
            'title' => 'Changelog',
            'url' => url('changelog-index'),
        ];
        $home['menu'][] = [
            'title' => 'Contact',
            'url' => url('info', ['title' => 'contact']),
        ];
        $home['menu'][] = [
            'title' => 'Rules',
            'url' => url('info', ['title' => 'rules']),
        ];

        $menu[] = $home;

        $menu[] = [
            'title' => 'News',
            'url' => url('news-index'),
        ];

        $forum = [
            'title' => 'Forum',
            'url' => url('forum-index'),
            'menu' => [],
        ];

        if($hasUserInfo && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD))
            $forum['menu'][] = [
                'title' => 'Leaderboard',
                'url' => url('forum-leaderboard'),
            ];

        $menu[] = $forum;

        $chatPath = $this->config->getString('sockChat.chatPath.normal');
        if(!empty($chatPath))
            $menu[] = [
                'title' => 'Chat',
                'url' => $chatPath,
            ];

        return $menu;
    }

    public function getUserMenu(?UserInfo $userInfo, bool $inBroomCloset): array {
        $menu = [];

        if($userInfo === null) {
            $menu[] = [
                'title' => 'Register',
                'url' => url('auth-register'),
                'icon' => 'fas fa-user-plus fa-fw',
            ];
            $menu[] = [
                'title' => 'Log in',
                'url' => url('auth-login'),
                'icon' => 'fas fa-sign-in-alt fa-fw',
            ];
        } else {
            $menu[] = [
                'title' => 'Profile',
                'url' => url('user-profile', ['user' => $userInfo->getId()]),
                'icon' => 'fas fa-user fa-fw',
            ];
            $menu[] = [
                'title' => 'Settings',
                'url' => url('settings-index'),
                'icon' => 'fas fa-cog fa-fw',
            ];
            $menu[] = [
                'title' => 'Search',
                'url' => url('search-index'),
                'icon' => 'fas fa-search fa-fw',
            ];

            if(!$this->hasActiveBan($userInfo) && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_GENERAL_CAN_MANAGE)) {
                // restore behaviour where clicking this button switches between
                // site version and broom version
                if($inBroomCloset)
                    $menu[] = [
                        'title' => 'Exit Broom Closet',
                        'url' => url('index'),
                        'icon' => 'fas fa-door-open fa-fw',
                    ];
                else
                    $menu[] = [
                        'title' => 'Enter Broom Closet',
                        'url' => url('manage-index'),
                        'icon' => 'fas fa-door-closed fa-fw',
                    ];
            }

            $menu[] = [
                'title' => 'Log out',
                'url' => url('auth-logout'),
                'icon' => 'fas fa-sign-out-alt fa-fw',
            ];
        }

        return $menu;
    }

    public function setUpHttp(): void {
        $this->router = new HttpFx;
        $this->router->use('/', function($response) {
            $response->setPoweredBy('Misuzu');
        });

        $this->registerErrorPages();
        $this->registerHttpRoutes();
    }

    public function dispatchHttp(?HttpRequest $request = null): void {
        $this->router->dispatch($request);
    }

    private function registerErrorPages(): void {
        $this->router->addErrorHandler(400, function($response) {
            $response->setContent(Template::renderRaw('errors.400'));
        });
        $this->router->addErrorHandler(403, function($response) {
            $response->setContent(Template::renderRaw('errors.403'));
        });
        $this->router->addErrorHandler(404, function($response) {
            $response->setContent(Template::renderRaw('errors.404'));
        });
        $this->router->addErrorHandler(500, function($response) {
            $response->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html'));
        });
        $this->router->addErrorHandler(503, function($response) {
            $response->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html'));
        });
    }

    private function registerHttpRoutes(): void {
        new HomeRoutes(
            $this->router, $this->config, $this->dbConn, $this->authInfo,
            $this->changelog, $this->comments, $this->counters, $this->news,
            $this->users
        );

        new AssetsRoutes($this->router, $this->authInfo, $this->bans, $this->users);

        new InfoRoutes($this->router);

        new NewsRoutes(
            $this->router, $this->config, $this->authInfo,
            $this->news, $this->users, $this->comments
        );

        new ChangelogRoutes(
            $this->router, $this->config, $this->changelog,
            $this->users, $this->authInfo, $this->comments
        );

        new SharpChatRoutes(
            $this->router, $this->config->scopeTo('sockChat'),
            $this->bans, $this->emotes, $this->users,
            $this->sessions, $this->authInfo,
            $this->createAuthTokenPacker(...)
        );

        new SatoriRoutes(
            $this->dbConn, $this->config->scopeTo('satori'),
            $this->router, $this->users, $this->profileFields
        );

        // below is still only otherwise available as stinky php files

        $this->router->get('/auth.php', function($response, $request) {
            $response->redirect(url([
                'logout' => 'auth-logout',
                'reset' => 'auth-reset',
                'forgot' => 'auth-forgot',
                'register' => 'auth-register',
            ][$request->getParam('m')] ?? 'auth-login'), true);
        });

        $this->router->get('/settings.php', function($response) {
            $response->redirect(url('settings-index'), true);
        });
    }
}
