<?php

namespace IMATHUZH\Qfq\Core\Helper\Version;

use IMATHUZH\Qfq\Core\Helper\Path;

/**
 * Helper class for TYPO3 extension settings management.
 * Handles getting and setting extension configurations.
 */
class ExtensionSettings
{
    /**
     * Get extension settings for all extensions that have ext_conf_template.txt
     * Returns template content and current values in inputCollectionJson format
     *
     * @param array $t3Config TYPO3 configuration array (from LocalConfiguration.php)
     * @return array [extKey => ['template' => string, 'values' => json]]
     */
    public static function getExtensionSettings(array $t3Config): array
    {
        $extensions = ExtensionDeploy::getAllExtensionsInfo();
        $result = [];

        // Get extension configurations from TYPO3 config
        $extConfigs = $t3Config['EXTENSIONS'] ?? [];

        foreach ($extensions as $extKey => $packagePath) {
            $templatePath = Path::absoluteApp() . '/' . $packagePath . 'ext_conf_template.txt';

            if (!file_exists($templatePath)) {
                continue;
            }

            $templateContent = file_get_contents($templatePath);
            if ($templateContent === false) {
                continue;
            }

            // Get current values for this extension
            $currentValues = $extConfigs[$extKey] ?? [];

            // Parse template and build values array
            $values = self::parseExtConfTemplate($templateContent, $currentValues);

            // Resolve LLL:EXT: references in template content
            $resolvedTemplate = self::resolveLLLReferences($templateContent);

            $result[$extKey] = [
                'template' => $resolvedTemplate,
                'values' => $values,
            ];
        }

        return $result;
    }

    /**
     * Resolve all LLL:EXT: language references in a string
     * Replaces LLL:EXT:extension/path/file.xlf:key with the actual translated text
     *
     * @param string $content Content containing LLL:EXT: references
     * @return string Content with resolved references
     */
    private static function resolveLLLReferences(string $content): string
    {
        // Cache for loaded XLF files to avoid re-reading
        static $xlfCache = [];

        // Pattern to match LLL:EXT:extension/path/to/file.xlf:translationKey
        $pattern = '/LLL:EXT:([^:]+):([a-zA-Z0-9_.\-]+)/';

        return preg_replace_callback($pattern, function ($matches) use (&$xlfCache) {
            $filePath = $matches[1]; // e.g., "oidc_client/Resources/Private/Language/locallang.xlf"
            $translationKey = $matches[2]; // e.g., "settings.oidcScopes"

            // Build full path to XLF file
            $fullPath = Path::absoluteApp() . '/typo3conf/ext/' . $filePath;

            // Check if file exists
            if (!file_exists($fullPath)) {
                // Try alternative path for system extensions
                $fullPath = Path::absoluteApp() . '/typo3/sysext/' . $filePath;
                if (!file_exists($fullPath)) {
                    return $matches[0]; // Return original if file not found
                }
            }

            // Load XLF file from cache or parse it
            if (!isset($xlfCache[$fullPath])) {
                $xlfCache[$fullPath] = self::parseXlfFile($fullPath);
            }

            // Return translated value or original reference if not found
            return $xlfCache[$fullPath][$translationKey] ?? $matches[0];
        }, $content);
    }

    /**
     * Parse an XLF (XLIFF) file and return translations as key => value array
     *
     * @param string $filePath Path to the XLF file
     * @return array Translations as [key => value]
     */
    private static function parseXlfFile(string $filePath): array
    {
        $translations = [];

        $content = file_get_contents($filePath);
        if ($content === false) {
            return $translations;
        }

        // Suppress XML errors and handle them gracefully
        libxml_use_internal_errors(true);
        $xml = simplexml_load_string($content);
        libxml_clear_errors();

        if ($xml === false) {
            return $translations;
        }

        // Handle XLIFF 1.2 format (most common in TYPO3)
        // Structure: xliff/file/body/trans-unit[@id]/source
        foreach ($xml->file->body->{'trans-unit'} ?? [] as $unit) {
            $id = (string)($unit['id'] ?? '');
            $source = (string)($unit->source ?? '');

            if ($id !== '' && $source !== '') {
                $translations[$id] = $source;
            }
        }

        return $translations;
    }

    /**
     * Parse TYPO3 FlexForms XML without TYPO3 dependencies
     * Returns array structure: [sheetName => [fieldName => value, ...], ...]
     *
     * @param string $xml FlexForms XML string
     * @return array Parsed data as [sheetName => [fieldName => value]]
     */
    public static function parseFlexFormsXml(string $xml): array
    {
        $result = [];

        if (empty($xml)) {
            return $result;
        }

        // Suppress XML errors and handle them gracefully
        libxml_use_internal_errors(true);
        $doc = simplexml_load_string($xml);
        libxml_clear_errors();

        if ($doc === false) {
            return $result;
        }

        // Navigate to data element
        if (!isset($doc->data)) {
            return $result;
        }

        // Iterate over sheets
        foreach ($doc->data->sheet as $sheet) {
            $sheetIndex = (string)($sheet['index'] ?? 'sDEF');

            // Get language element (usually lDEF)
            $language = $sheet->language ?? null;
            if ($language === null) {
                continue;
            }

            $result[$sheetIndex] = [];

            // Iterate over fields
            foreach ($language->field as $field) {
                $fieldIndex = (string)($field['index'] ?? '');
                if (empty($fieldIndex)) {
                    continue;
                }

                // Get value (usually vDEF)
                $value = '';
                if (isset($field->value)) {
                    $value = (string)$field->value;
                }

                // Remove 'settings.' prefix for consistency with FlexFormService output
                $fieldName = preg_replace('/^settings\./', '', $fieldIndex);
                $result[$sheetIndex][$fieldName] = $value;
            }
        }

        return $result;
    }

    /**
     * Parse ext_conf_template.txt and build values array in inputCollectionJson format
     * Format: {"category": {"varName": {"value": "currentValue"}, ...}, ...}
     *
     * @param string $templateContent
     * @param array $currentValues
     * @return array
     */
    private static function parseExtConfTemplate(string $templateContent, array $currentValues): array
    {
        $result = [];
        $lines = explode("\n", $templateContent);
        $currentCategory = 'general';

        for ($i = 0; $i < count($lines); $i++) {
            $line = trim($lines[$i]);

            // Check for category comment: # cat=category/subcategory; type=...; label=...
            if (preg_match('/^#\s*cat\s*=\s*([^\/;]+)/', $line, $matches)) {
                $currentCategory = trim($matches[1]);
            }

            // Check for variable assignment: varName = defaultValue
            if (preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/', $line, $matches)) {
                $varName = trim($matches[1]);

                // Use current value if set, otherwise empty string
                $value = $currentValues[$varName] ?? '';

                // Initialize category if not exists
                if (!isset($result[$currentCategory])) {
                    $result[$currentCategory] = [];
                }

                $result[$currentCategory][$varName] = ['value' => $value];
            }
        }

        return $result;
    }

    /**
     * Set extension settings from provided config
     * Only updates extensions that are included in the config
     * Directly modifies LocalConfiguration.php without requiring full TYPO3 bootstrap
     *
     * @param array|string $config JSON string or array with extKey => values mapping
     *                             Values format: {"category": {"varName": {"value": "val"}, ...}, ...}
     * @return array Results with processed extensions and changes
     */
    public static function setExtensionSettings(array|string $config): 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());
            }
        }

        // Read LocalConfiguration.php directly
        $localConfigPath = Path::absoluteTypo3Conf(CONFIG_T3);
        if (!file_exists($localConfigPath)) {
            throw new \RuntimeException('LocalConfiguration.php not found: ' . $localConfigPath);
        }

        $localConfig = include $localConfigPath;
        if (!is_array($localConfig)) {
            throw new \RuntimeException('Invalid LocalConfiguration.php format');
        }

        // Ensure EXTENSIONS array exists
        if (!isset($localConfig['EXTENSIONS'])) {
            $localConfig['EXTENSIONS'] = [];
        }

        $results = [];
        $configChanged = false;

        foreach ($config as $extKey => $extData) {
            $result = [
                'extKey' => $extKey,
                'changes' => [],
            ];

            // Handle format with 'template' and 'values' keys (same as response format)
            if (is_array($extData) && isset($extData['values'])) {
                $values = $extData['values'];
                // If 'values' is a JSON string, decode it
                if (is_string($values)) {
                    $values = json_decode($values, true) ?? [];
                }
            } else {
                $values = $extData;
            }

            // Flatten the hierarchical format to TYPO3's flat format
            $flatValues = self::flattenExtSettingValues($values);

            // Get current values for this extension from LocalConfiguration
            $currentValues = $localConfig['EXTENSIONS'][$extKey] ?? [];

            // Track changes and merge values
            foreach ($flatValues as $varName => $newValue) {
                $oldValue = $currentValues[$varName] ?? null;

                if ($oldValue !== $newValue) {
                    $result[API_RESULT_KEY_CHANGES][] = [
                        API_CHANGE_KEY_FIELD => $varName,
                        API_CHANGE_KEY_OLD => $oldValue,
                        API_CHANGE_KEY_NEW => $newValue,
                    ];
                    $currentValues[$varName] = $newValue;
                    $configChanged = true;
                }
            }

            // Update the extension config in the array
            if (!empty($result[API_RESULT_KEY_CHANGES])) {
                $localConfig['EXTENSIONS'][$extKey] = $currentValues;
            }

            $results[] = $result;
        }

        // Write back to LocalConfiguration.php if changed
        if ($configChanged) {
            $result = self::writeLocalConfiguration($localConfigPath, $localConfig);
            if ($result !== true) {
                return [
                    OIDC_KEY_PROCESSED => count($results),
                    'configUpdated' => false,
                    OIDC_KEY_ERROR => $result,
                    OIDC_KEY_RESULTS => $results,
                ];
            }

            // Clear cache
            BackendUserManager::clearSystemCache();
        }

        return [
            OIDC_KEY_PROCESSED => count($results),
            'configUpdated' => $configChanged,
            OIDC_KEY_RESULTS => $results,
        ];
    }

    /**
     * Write LocalConfiguration.php with proper PHP array formatting
     *
     * @param string $path Path to LocalConfiguration.php
     * @param array $config Configuration array to write
     * @return bool|string True on success, error message on failure
     */
    private static function writeLocalConfiguration(string $path, array $config): bool|string
    {
        // Generate PHP code for the configuration array
        $phpCode = "<?php\nreturn " . self::arrayExport($config, 0) . ";\n";

        // Write to file
        $result = file_put_contents($path, $phpCode, LOCK_EX);
        if ($result === false) {
            return 'Failed to write LocalConfiguration.php';
        }

        // Clear opcache if available
        if (function_exists('opcache_invalidate')) {
            opcache_invalidate($path, true);
        }

        return true;
    }

    /**
     * Export array as PHP code with proper formatting (like var_export but cleaner)
     *
     * @param mixed $var Variable to export
     * @param int $indent Current indentation level
     * @return string PHP code representation
     */
    public static function arrayExport(mixed $var, int $indent): string
    {
        if (!is_array($var)) {
            return var_export($var, true);
        }

        $spaces = str_repeat('    ', $indent);
        $innerSpaces = str_repeat('    ', $indent + 1);

        // Check if array is sequential (list) or associative
        $isSequential = array_keys($var) === range(0, count($var) - 1);

        $items = [];
        foreach ($var as $key => $value) {
            $exportedValue = self::arrayExport($value, $indent + 1);
            if ($isSequential) {
                $items[] = $innerSpaces . $exportedValue;
            } else {
                $exportedKey = var_export($key, true);
                $items[] = $innerSpaces . $exportedKey . ' => ' . $exportedValue;
            }
        }

        if (empty($items)) {
            return '[]';
        }

        return "[\n" . implode(",\n", $items) . ",\n" . $spaces . ']';
    }

    /**
     * Flatten hierarchical ext setting values to TYPO3's flat format
     * Input: {"category": {"varName": {"value": "val"}, ...}, ...}
     * Output: {"varName": "val", ...}
     *
     * @param array $values
     * @return array
     */
    public static function flattenExtSettingValues(array $values): array
    {
        $flat = [];

        foreach ($values as $categoryOrVar => $content) {
            if (is_array($content)) {
                // Check if this is a category (contains arrays with 'value' key)
                // or a direct value entry
                foreach ($content as $varName => $varContent) {
                    if (is_array($varContent) && array_key_exists('value', $varContent)) {
                        // Format: {"category": {"varName": {"value": "val"}}}
                        $flat[$varName] = $varContent['value'];
                    } elseif (is_array($varContent)) {
                        // Nested category, recurse
                        foreach ($varContent as $nestedVar => $nestedContent) {
                            if (is_array($nestedContent) && array_key_exists('value', $nestedContent)) {
                                $flat[$nestedVar] = $nestedContent['value'];
                            }
                        }
                    } else {
                        // Direct flat format: {"varName": "val"}
                        $flat[$varName] = $varContent;
                    }
                }
            } else {
                // Direct flat format at top level
                $flat[$categoryOrVar] = $content;
            }
        }

        return $flat;
    }
}
