<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 4/8/17
 * Time: 6:53 PM
 */

namespace IMATHUZH\Qfq\Core\Store;


use IMATHUZH\OidcClient\Service\UserStorage;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Exception\Thrower;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Typo3\T3Handler;


/**
 * Class T3Info
 * @package qfq
 */
class T3Info {

    /**
     * Collect some global T3 variables and return them as array.
     *
     * @return array
     *
     * @todo v12 migration: access feUser via $request->getAttribute('frontend.user')
     */
    public static function getVars() {

        $t3vars = array();

        $t3vars[TYPO3_FE_USER] = isset($GLOBALS["TSFE"]->fe_user->user["username"]) ? $GLOBALS["TSFE"]->fe_user->user["username"] : '';
        $t3vars[TYPO3_FE_USER_EMAIL] = isset($GLOBALS["TSFE"]->fe_user->user["email"]) ? $GLOBALS["TSFE"]->fe_user->user["email"] : '';

        $t3vars[TYPO3_FE_USER_UID] = isset($GLOBALS["TSFE"]->fe_user->user["uid"]) ? $GLOBALS["TSFE"]->fe_user->user["uid"] : '';

        $t3vars[TYPO3_FE_USER_GROUP] = isset($GLOBALS["TSFE"]->fe_user->user["usergroup"]) ? $GLOBALS["TSFE"]->fe_user->user["usergroup"] : '';

        // Will be set later
        $t3vars[TYPO3_TT_CONTENT_UID] = 'not set yet';

        $t3vars[TYPO3_PAGE_ID] = isset($GLOBALS["TSFE"]->id) ? $GLOBALS["TSFE"]->id : '';

        $t3vars[TYPO3_PAGE_ALIAS] = empty($GLOBALS["TSFE"]->page["alias"]) ? $t3vars[TYPO3_PAGE_ID] : $GLOBALS["TSFE"]->page["alias"];

        if (T3Handler::useSlugsInsteadOfPageAlias()) {
            $t3vars[TYPO3_PAGE_SLUG] = empty($GLOBALS["TSFE"]->page["slug"]) ? '/' : $GLOBALS["TSFE"]->page["slug"];
        } else {
            $t3vars[TYPO3_PAGE_SLUG] = $t3vars[TYPO3_PAGE_ALIAS]; // the Form Editor report (formEditor.qfqr) depends on this for T3 older than version 9.
        }

        $t3vars[TYPO3_PAGE_TITLE] = isset($GLOBALS["TSFE"]->page["title"]) ? $GLOBALS["TSFE"]->page["title"] : '';

        $t3vars[TYPO3_PAGE_TYPE] = isset($GLOBALS["TSFE"]->type) ? $GLOBALS["TSFE"]->type : '';

        $t3vars[TYPO3_PAGE_LANGUAGE] = self::getLanguageId();

        $t3vars[TYPO3_PAGE_LANGUAGE_PATH] = self::getLanguagePath($t3vars[TYPO3_PAGE_LANGUAGE]);

        $t3vars[TYPO3_BE_USER_LOGGED_IN] = self::beUserLoggedIn() ? 'yes' : 'no';

        $t3vars[TYPO3_BE_USER] = isset($GLOBALS["BE_USER"]->user["username"]) ? $GLOBALS["BE_USER"]->user["username"] : '';
        $t3vars[TYPO3_BE_USER_EMAIL] = isset($GLOBALS["BE_USER"]->user["email"]) ? $GLOBALS["BE_USER"]->user["email"] : '';
        $t3vars[TYPO3_BE_USER_UID] = isset($GLOBALS["BE_USER"]->user["uid"]) ? $GLOBALS["BE_USER"]->user["uid"] : '';

        $t3vars[TYPO3_PAGE_DESCRIPTION] = $GLOBALS["TSFE"]->page[T3DATA_DESCRIPTION] ?? '';

        $t3vars[TYPO3_PAGE_ABSTRACT] = $GLOBALS["TSFE"]->page[T3DATA_ABSTRACT] ?? '';

        $t3vars[TYPO3_PAGE_KEYWORDS] = $GLOBALS["TSFE"]->page[T3DATA_KEYWORDS] ?? '';

        if (defined('TYPO3_version')) {
            $t3vars[TYPO3_VERSION] = TYPO3_version;
        }

        /***** oidc data ******/

        if (class_exists(UserStorage::class)) {
            $oidcStorage = (new UserStorage($GLOBALS["TSFE"]->fe_user))->get() ?? [];
        } else {
            $oidcStorage = [];
        }

        $t3vars[TYPO3_OIDC_PROVIDER] = $oidcStorage['provider'] ?? '';
        $t3vars[TYPO3_OIDC_RESOURCE_ID] = $oidcStorage['resource_id'] ?? '';
        $t3vars[TYPO3_OIDC_SUB] = $oidcStorage['sub'] ?? '';
        $t3vars[TYPO3_OIDC_EMAIL] = $oidcStorage['email'] ?? '';
        if (isset($oidcStorage['email_verified'])) {
            $t3vars[TYPO3_OIDC_EMAIL_VERIFIED] = ($oidcStorage['email_verified'] === true) ? 'true' : 'false';
        } else {
            $t3vars[TYPO3_OIDC_EMAIL_VERIFIED] = 'null';
        }
        $t3vars[TYPO3_OIDC_GIVEN_NAME] = $oidcStorage['given_name'] ?? '';
        $t3vars[TYPO3_OIDC_FAMILY_NAME] = $oidcStorage['family_name'] ?? '';

        /***** oidc data end ******/


        return $t3vars;
    }

    /**
     * Returns the page language uid. This is only possible if QFQ is called via T3, not via API.
     *
     * @return string
     */
    public static function getLanguageId() {
        if (class_exists('TYPO3\CMS\Core\Context\Context')) {
            // Typo3 version >=9
            $context = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Context\Context::class);
            $languageAspect = $context->getAspect('language');
            // (previously known as TSFE->sys_language_uid)
            return $languageAspect->getId();
        } elseif (isset($GLOBALS["TSFE"]->sys_language_uid)) {
            // Typo3 version <=8
            return $GLOBALS["TSFE"]->sys_language_uid;
        }
        return '';
    }

    /**
     * Returns the base url with language path. This is only possible if QFQ is called via T3, not via API.
     *
     * @return string
     * @throws \UserReportException
     */
    public static function getBaseUrlLang() {
        if (!T3Handler::isTypo3Loaded()) {
            return null;
        }

        $lid = self::getLanguageId();
        if ($lid !== '' && is_numeric($lid) && T3Handler::typo3VersionGreaterEqual9()) {
            $lid = intval($lid);
            try {
                // In an API call Typo3 might have been loaded manually by QFQ at some earlier point. But the following line does not work in that case.
                $sf = new \TYPO3\CMS\Core\Site\SiteFinder();
            } catch (\Throwable $e) {
                return null;
            }
            $allSites = $sf->getAllSites();
            if (count($allSites) < 1) {
                return null;
            } else {
                $site = self::getSiteConfiguration($sf);
                $lang = $site->getLanguageById($lid);
                $base = $lang->getBase();
                return (string)$base;
            }
        }
        return null;
    }

    /**
     * Check if there is a Typo3 beUser is logged in. This is only possible if QFQ is called via T3, not via API
     *
     * @return bool  true: current Browser session is a logged in BE User.
     */
    public static function beUserLoggedIn() {

        if (T3Handler::isTypo3Loaded() && T3Handler::typo3VersionGreaterEqual9()) {
            // Typo3 version >=9
            $context = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Context\Context::class);
            return $context->getPropertyFromAspect('backend.user', 'isLoggedIn');
        } elseif (!empty($GLOBALS["TSFE"]->beUserLogin) && $GLOBALS["TSFE"]->beUserLogin === true) {
            // Typo3 version <=8
            return true;
        }
        return false;
    }

    /**
     * Returns the language path part. Helpful for forwardPage handling of forms in T3 >=V10
     *
     * @return string
     */
    public static function getLanguagePath($languageId): string {
        $languagePath = '';
        if (T3Handler::typo3VersionGreaterEqual10() && isset($GLOBALS['TYPO3_REQUEST'])) {
            $siteIdentifier = $GLOBALS['TYPO3_REQUEST']->getAttribute('site')->getIdentifier();
            $siteConfiguration = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Site\SiteFinder::class)->getSiteByIdentifier($siteIdentifier)->getConfiguration();
            $languages = $siteConfiguration['languages'];
            $actualLanguage = $languages[$languageId]['base'];
            $languagePath = trim($actualLanguage, '/');
        }

        return $languagePath;
    }

    /**
     * Returns the site configuration from current page. Used for T3 >=V10.
     *
     * object $sf
     * @return object
     */
    public static function getSiteConfiguration($sf): object {
        if (T3Handler::typo3VersionGreaterEqual10() && isset($GLOBALS['TYPO3_REQUEST'])) {
            $siteIdentifier = $GLOBALS['TYPO3_REQUEST']->getAttribute('site')->getIdentifier();
            $actualSite = $sf->getSiteByIdentifier($siteIdentifier);
        } else {
            $allSites = $sf->getAllSites();
            $actualSite = reset($allSites);
        }

        return $actualSite;
    }

    /**
     * Get typo 3 extension informations
     *
     *
     * @return array
     */
    public static function getAllExtensionsInfo(): array {
        $packageStatesPath = Path::absoluteTypo3Conf('PackageStates.php');

        if (!file_exists($packageStatesPath)) {
            throw new \Exception('PackageStates.php does not exist at the specified path.');
        }

        $packageStates = include $packageStatesPath;

        $extensions = [];
        if (isset($packageStates['packages'])) {
            foreach ($packageStates['packages'] as $key => $package) {
                $extensions[$key] = $package['packagePath'];
            }
        }

        return $extensions;
    }

    /**
     * Get all backend users with admin and maintainer status
     *
     * @param Database $db
     * @param array $systemMaintainers
     * @return array
     */
    public static function getAllBackendUsers(Database $db, array $systemMaintainers = []): array
    {
        $users = $db->sql(
            "SELECT uid, username, admin, email, realName, disable FROM be_users WHERE deleted = 0",
            ROW_REGULAR
        );

        return array_map(function ($user) use ($systemMaintainers) {
            $uid = (int)$user['uid'];
            return [
                'uid' => $uid,
                'username' => $user['username'],
                'realName' => $user['realName'],
                'email' => $user['email'],
                'isAdmin' => (bool)$user['admin'],
                'isMaintainer' => in_array($uid, $systemMaintainers, true),
                'isDisabled' => (bool)$user['disable'],
            ];
        }, $users);
    }

    /**
     * Ensure backend users have correct admin/maintainer/disabled status
     *
     * @param array|string $config JSON string or array with username => settings mapping
     * @param Database $db
     * @param array $systemMaintainers Current system maintainers (will be updated by reference)
     * @return array
     */
    public static function ensureAdminMaintainers(array|string $config, Database $db, array &$systemMaintainers): array
    {
        // Accept JSON string or array
        if (is_string($config)) {
            $config = json_decode($config, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
            }
        }

        $originalMaintainers = $systemMaintainers;
        $results = [];

        foreach ($config as $username => $settings) {
            $isAdmin = (bool)($settings['isAdmin'] ?? false);
            $isMaintainer = (bool)($settings['isMaintainer'] ?? false);
            $isDisabled = (bool)($settings['isDisabled'] ?? false);
            $delete = (bool)($settings['delete'] ?? false);

            $result = [
                'username' => $username,
                'uid' => null,
                'created' => false,
                'changes' => [],
            ];

            // Check if user exists
            $existing = $db->sql(
                "SELECT uid, admin, disable, deleted FROM be_users WHERE username = ?  AND deleted = ?",
                ROW_EXPECT_0_1,
                [$username, 0]
            );

            if (!empty($existing)) {
                $uid = (int)$existing['uid'];
                $result['uid'] = $uid;
                $updates = [];

                // Check admin flag
                if ((bool)$existing['admin'] !== $isAdmin) {
                    $updates[] = 'admin = ' . ($isAdmin ? 1 : 0);
                    $result['changes'][] = $isAdmin ? 'admin granted' : 'admin revoked';
                }

                // Check disable flag
                if ((bool)$existing['disable'] !== $isDisabled) {
                    $updates[] = 'disable = ' . ($isDisabled ? 1 : 0);
                    $result['changes'][] = $isDisabled ? 'disabled' : 'enabled';
                }

                if ((bool)$existing['deleted'] !== $delete) {
                    $updates[] = 'deleted = ' . ($delete ? 1 : 0);
                    $result['changes'][] = $delete ? 'deleted' : 'revoke delete';
                }

                if (!empty($updates)) {
                    $updates[] = 'tstamp = ' . time();
                    $db->sql("UPDATE be_users SET " . implode(', ', $updates) . " WHERE uid = ?", ROW_REGULAR, [$uid]);
                }
            } else if (!$delete) {
                // Create new user
                $db->sql(
                    "INSERT INTO be_users (username, admin, disable, password, tstamp, crdate) VALUES (?, ?, ?, '', ?, ?)",
                    ROW_REGULAR,
                    [$username, $isAdmin ? 1 : 0, $isDisabled ? 1 : 0, time(), time()]
                );
                $uid = $db->getLastInsertId();
                $result['uid'] = $uid;
                $result['created'] = true;
                $result['changes'][] = 'user created';
            }

            // Handle maintainer status
            $isCurrentlyMaintainer = in_array($uid, $systemMaintainers, true);

            if ($isMaintainer && !$isCurrentlyMaintainer) {
                $systemMaintainers[] = $uid;
                $result['changes'][] = 'maintainer added';
            } elseif (!$isMaintainer && $isCurrentlyMaintainer) {
                $systemMaintainers = array_values(array_filter($systemMaintainers, fn($id) => $id !== $uid));
                $result['changes'][] = 'maintainer removed';
            }

            $results[] = $result;
        }

        // Check if maintainers changed
        $maintainersChanged = $systemMaintainers !== $originalMaintainers;

        // Write to config if changed
        if ($maintainersChanged) {
            self::setSystemMaintainersToConfig(array_values($systemMaintainers));
        }

        return [
            'processed' => count($results),
            'maintainersUpdated' => $maintainersChanged,
            'details' => $results,
        ];
    }

    /**
     * Write systemMaintainers to LocalConfiguration.php
     *
     * @param array $maintainers
     */
    private static function setSystemMaintainersToConfig(array $maintainers): void
    {
        $localConfPath = Path::absoluteTypo3Conf('LocalConfiguration.php');
        if (!file_exists($localConfPath)) {
            return;
        }

        $config = include $localConfPath;
        $config['SYS']['systemMaintainers'] = $maintainers;

        $content = "<?php\nreturn " . var_export($config, true) . ";\n";
        file_put_contents($localConfPath, $content);

        // Clear TYPO3 system cache if available
        self::clearSystemCache();
    }

    /**
     * Clear TYPO3 system cache
     */
    private static function clearSystemCache(): void
    {
        $possiblePaths = [
            Path::absoluteTypo3Conf('../typo3temp/var/cache'), // Upgrade or legacy installations
            Path::absoluteTypo3Conf('../var/cache'),           // Fresh TYPO3 >= 11
        ];

        foreach ($possiblePaths as $cacheDir) {
            if (is_dir($cacheDir)) {
                $codeCacheDir = $cacheDir . '/code/cache_core';
                if (is_dir($codeCacheDir)) {
                    array_map('unlink', glob($codeCacheDir . '/*') ?: []);
                }
            }
        }
    }

    /**
     * Get active frontend users information (count and activity timestamps)
     *
     * @param Database $db
     * @return array
     */
    public static function getActiveFrontendUsersInfo(Database $db): array
    {
        $result = $db->sql(
            "SELECT COUNT(DISTINCT ses_userid) as count, MIN(ses_tstamp) as firstTimestamp, MAX(ses_tstamp) as lastTimestamp FROM fe_sessions WHERE ses_userid > 0",
            ROW_EXPECT_0_1
        );

        return [
            'activeCount' => (int)($result['count'] ?? 0),
            'firstActiveDatetime' => $result['firstTimestamp'] ? date('Y-m-d H:i:s', $result['firstTimestamp']) : null,
            'lastActiveDatetime' => $result['lastTimestamp'] ? date('Y-m-d H:i:s', $result['lastTimestamp']) : null
        ];
    }

}