<?php

namespace IMATHUZH\Qfq\Core\Helper;

use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\Version\ExtensionDeploy;
use IMATHUZH\Qfq\Core\Helper\Version\BackendUserManager;
use IMATHUZH\Qfq\Core\Helper\Version\ExtensionSettings;
use IMATHUZH\Qfq\Core\Helper\Version\OidcClientModule;
use IMATHUZH\Qfq\Core\Store\Client;
use IMATHUZH\Qfq\Core\Store\Config;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Store\T3Info;

/**
 * Central class for version.php API functionality.
 * Handles API requests and coordinates specialized subclasses for
 * extension management, backend users, extension settings, and OIDC module data.
 */
class Version
{
    /**
     * Words to mask in config file contents for security
     */
    private const BLACKLIST_WORDS = [
        'DB_1_PASSWORD',
        'DB_2_PASSWORD',
        'DB_3_PASSWORD',
        'DB_4_PASSWORD',
        'LDAP_1_PASSWORD',
        'ENCRYPTION_KEY',
        'installToolPassword',
        'password',
        'secretKey',
        'forwardServiceSecret',
        'encryptionKey',
    ];

    // ========================================
    // API Request Handler
    // ========================================

    /**
     * Main entry point for API requests.
     * Handles authentication, request parsing, and returns response data.
     *
     * @return array [API_STORE_DATA_KEY => array, API_STATUS => int]
     * @throws \Exception On authentication or processing errors
     */
    public static function process(): array {
        $store = Store::getInstance();
        $config = self::parseRequestBody();

        // Authenticate request
        $headers = Client::getParam();
        self::authenticate($store, $headers);

        // Handle deploy mode (isolated)
        if (array_key_exists(API_VERSION_DEPLOY, $config) && is_array($config[API_VERSION_DEPLOY])) {
            $data = self::handleDeployMode($config);
            $allSuccess = !in_array(false, array_column($data[API_VERSION_DEPLOY], API_ANSWER_STATUS_SUCCESS), true);
            return [API_STORE_DATA_KEY => $data, API_STATUS => $allSuccess ? 200 : 207];
        }

        // Setup database and config
        $t3DbConfig = [];
        $db = self::setupTypo3Database($store, $t3DbConfig);
        $systemMaintainers = $t3DbConfig[TYPO3_CONF_SYSTEM_MAINTAINERS] ?? [];

        // Determine request mode
        $hasExtModuleRequest = array_key_exists(API_VERSION_CHECK_EXT_MODULE, $config);
        $hasBeUserRequest = array_key_exists(API_VERSION_CHECK_BE_USER, $config);
        $extModuleOnlyMode = $hasExtModuleRequest && !$hasBeUserRequest;

        // Handle request based on mode
        if ($extModuleOnlyMode) {
            $data = self::handleExtModuleMode($config, $db);
        } else {
            $data = self::handleStandardMode($config, $db, $t3DbConfig, $systemMaintainers, $store);
        }

        return [API_STORE_DATA_KEY => $data, API_STATUS => 200];
    }

    /**
     * Authenticate API request using token from headers
     *
     * @param Store $store
     * @param array $headers
     * @throws \Exception
     */
    private static function authenticate(Store $store, array $headers): void {
        $providedToken = $headers[HTTP_HEADER_AUTHORIZATION] ?? '';
        $validToken = $store::getVar(SYSTEM_QFQ_VERSION_API_TOKEN, STORE_SYSTEM);

        if (empty($validToken)) {
            throw new \Exception("Server configuration error: API token not set", 500);
        }

        if (empty($providedToken)) {
            throw new \Exception("No API token provided", 401);
        }

        if ($providedToken !== $validToken) {
            throw new \Exception("Invalid API token", 401);
        }
    }

    /**
     * Parse JSON request body
     *
     * @return array Parsed config or empty array
     */
    private static function parseRequestBody(): array {
        $json = file_get_contents('php://input');
        if (empty($json)) {
            return [];
        }

        $config = json_decode($json, true);
        if ($config === null && json_last_error() !== JSON_ERROR_NONE) {
            return [];
        }

        return $config;
    }

    /**
     * Handle deploy mode - deploy extensions and return with version info
     *
     * @param array $config Request config containing deploy data
     * @return array Response data with deploy results and extension versions
     */
    private static function handleDeployMode(array $config): array {
        $deployResults = ExtensionDeploy::deployExtensions($config[API_VERSION_DEPLOY]);

        $data = [API_VERSION_DEPLOY => $deployResults];

        // Get extension version info for successfully deployed extensions (invalidate cache to get fresh versions)
        $allExtensions = ExtensionDeploy::getAllExtensionsInfo();
        $allVersions = Config::getExtVersion($allExtensions, false, true);

        // Add version info at top level (same structure as standard mode)
        foreach (array_keys($config[API_VERSION_DEPLOY]) as $extKey) {
            if (isset($deployResults[$extKey]) && $deployResults[$extKey][API_ANSWER_STATUS_SUCCESS]) {
                if (isset($allVersions[$extKey])) {
                    $data[$extKey] = $allVersions[$extKey];
                }
            }
        }

        return $data;
    }

    /**
     * Handle extModule-only mode - return only extension module data
     *
     * @param array $config Request config
     * @param Database|null $db Database connection
     * @return array Response data
     */
    private static function handleExtModuleMode(array $config, ?Database $db): array {
        if ($db === null) {
            return [API_VERSION_CHECK_EXT_MODULE => [API_ANSWER_STATUS_ERROR => 'Database not available']];
        }

        // Set extension module data if provided
        if (!empty($config[API_VERSION_CHECK_EXT_MODULE])) {
            OidcClientModule::setExtModuleData($config[API_VERSION_CHECK_EXT_MODULE], $db);
        }

        // Get and return extension module data
        return [API_VERSION_CHECK_EXT_MODULE => OidcClientModule::getExtModuleData($db)];
    }

    /**
     * Handle standard mode - return full response with all data
     *
     * @param array $config Request config
     * @param Database|null $db Database connection
     * @param array $t3DbConfig TYPO3 database config
     * @param array $systemMaintainers System maintainer UIDs
     * @param Store $store Store instance
     * @return array Response data
     */
    private static function handleStandardMode(
        array $config,
        ?Database $db,
        array $t3DbConfig,
        array $systemMaintainers,
        Store $store
    ): array {
        $hasExtModuleRequest = array_key_exists(API_VERSION_CHECK_EXT_MODULE, $config);
        $hasBeUserRequest = array_key_exists(API_VERSION_CHECK_BE_USER, $config);

        // Start with base response data
        $data = self::prepareBaseResponseData($store);

        // Process write operations first

        // Set backend users based on passed config
        if ($hasBeUserRequest && $db !== null && !empty($config[API_VERSION_CHECK_BE_USER])) {
            BackendUserManager::ensureAdminMaintainers($config[API_VERSION_CHECK_BE_USER], $db, $systemMaintainers);
        }

        // Set extension settings based on passed config
        if (isset($config[API_VERSION_CHECK_EXT_SETTING])) {
            try {
                ExtensionSettings::setExtensionSettings($config[API_VERSION_CHECK_EXT_SETTING]);
            } catch (\Exception $e) {
                // Silently ignore write errors
            }
        }

        // Set extension module data (only if beUser also requested)
        if ($hasExtModuleRequest && $hasBeUserRequest && $db !== null) {
            if (!empty($config[API_VERSION_CHECK_EXT_MODULE])) {
                OidcClientModule::setExtModuleData($config[API_VERSION_CHECK_EXT_MODULE], $db);
            }
        }

        // Gather response data

        // Get backend and frontend user info
        if ($db !== null) {
            $allBeUsers = BackendUserManager::getAllBackendUsers($db, $systemMaintainers);
            $feUserInfo = BackendUserManager::getActiveFrontendUsersInfo($db);
            $data[API_VERSION_CHECK_BE_USER] = array_values($allBeUsers);
            $data[API_VERSION_CHECK_FE_USER] = $feUserInfo;
        }

        // Get extension settings
        $t3ConfigPath = Path::absoluteTypo3Conf(CONFIG_T3);
        if (file_exists($t3ConfigPath)) {
            $t3Config = include $t3ConfigPath;
            $data[API_VERSION_CHECK_EXT_SETTING] = ExtensionSettings::getExtensionSettings($t3Config);
        }

        // Get extension module data (only if both extModule and beUser requested)
        if ($hasExtModuleRequest && $hasBeUserRequest && $db !== null) {
            $extModuleData = OidcClientModule::getExtModuleData($db);
            if (!empty($extModuleData)) {
                $data[API_VERSION_CHECK_EXT_MODULE] = $extModuleData;
            }
        }

        // Add extensions info
        $extensions = ExtensionDeploy::getAllExtensionsInfo();
        $extensions = Config::getExtVersion($extensions);
        $data = array_merge($data, $extensions);

        // Add config file contents
        $filePaths = [
            Path::absoluteConf(CONFIG_QFQ_JSON),
            Path::absoluteTypo3Conf(CONFIG_T3),
        ];
        $contents = HelperFile::getFileContents($filePaths, self::BLACKLIST_WORDS, $t3DbConfig);
        $data = array_merge($data, $contents);

        return $data;
    }

    /**
     * Prepare base response data (url, hostname, project, typo3 version, deb packages)
     *
     * @param Store $store
     * @return array Base response data
     */
    private static function prepareBaseResponseData(Store $store): array {
        $vars = T3Info::getVars();

        return [
            API_VERSION_KEY_URL => $store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM),
            API_VERSION_KEY_HOSTNAME => gethostname(),
            API_VERSION_KEY_PROJECT => $store::getVar(SYSTEM_T3_SITE_NAME, STORE_SYSTEM),
            API_VERSION_KEY_TYPO3 => $vars[TYPO3_VERSION] ?? 'unknown',
            API_VERSION_KEY_DEB_PACKAGES => Config::getServerInfo(),
        ];
    }

    /**
     * Setup TYPO3 database connection
     *
     * @param Store $store
     * @param array $t3DbConfig Will be populated with T3 database config
     * @return Database|null Database instance or null if not available
     */
    private static function setupTypo3Database(Store $store, array &$t3DbConfig): ?Database {
        // Get file contents and T3 credentials
        $filePaths = [
            Path::absoluteConf(CONFIG_QFQ_JSON),
            Path::absoluteTypo3Conf(CONFIG_T3),
        ];
        HelperFile::getFileContents($filePaths, self::BLACKLIST_WORDS, $t3DbConfig);

        if (empty($t3DbConfig)) {
            return null;
        }

        // Find free database index
        $dbIndexData = $store->getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM);
        $dbIndexQfq = $store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM);
        $dbIndexT3 = $dbIndexQfq;

        for ($i = 1; $i <= 3; $i++) {
            if ($dbIndexData !== $i && $dbIndexQfq !== $i) {
                $dbIndexT3 = $i;
                break;
            }
        }

        // Create database connection
        $dbConfig = [
            'DB_' . $dbIndexT3 . '_USER' => $t3DbConfig[TYPO3_DB_USER],
            'DB_' . $dbIndexT3 . '_PASSWORD' => $t3DbConfig[TYPO3_DB_PASSWORD],
            'DB_' . $dbIndexT3 . '_SERVER' => $t3DbConfig[TYPO3_DB_HOST],
            'DB_' . $dbIndexT3 . '_NAME' => $t3DbConfig[TYPO3_DB_NAME],
        ];

        return new Database($dbIndexT3, $dbConfig);
    }
}
