<?php

/**
 * Configuration instance which reads configuration from environment variables,
 * or $options[env_config] in Mage::run/Mage::init.
 *
 * ## General configuration
 *
 * * `MAGENTO_CRYPT_KEY`: Encryption key used by Magento
 *     (`config/global/crypt/key`).
 * * `MAGENTO_CACHE_BACKEND`: Cache type to use for Magento
 *     (`config/global/cache`).
 * * `APP_ENV`: If set to `production` or `staging` it will disable
 *     symlink-support (`default/dev/template/allow_symlink`).
 *
 * ## Database:
 *
 * Database configuration is specified as one or more Database Source Names,
 * all prefixed by `RESOURCE_`. The following characters will be lowercased
 * when added to configuration, eg. `RESOURCE_MY_DATA` will be made available
 * as `my_data` connection.
 *
 * Extra parameters can be added as query-string parameters:
 *
 *  * `initStatements`: The initial statement to run after connecting.
 *  * `profiler`: If the database profiler should be enabled.
 *
 * Example:
 *
 * ```
 * RESOURCE_DEFAULT_SETUP=mysql://admin:foobar@thedatabase:1234/mydatabase?profiler=1
 * RESOURCE_CUSTOM_WRITE=mysql://root@otherdatabase:1234/insecuredatabase?initStatements=SET NAMES latin1
 * ```
 *
 * Database connections can also refer to other connections by name through using
 * the protocol "use":
 *
 * ```
 * RESOURCE_MY_RESOURCE=use://default_setup
 * RESOURCE_MY_WRITE=use://my_resource
 * ```
 *
 * ## Required keys
 *
 * * `MAGENTO_CRYPT_KEY`
 * * `RESOURCE_DEFAULT_SETUP`
 *
 * Usage:
 *
 *   Mage::run("store_code", "store", [
 *       "config_model" => "Awardit_Magento_EnvConfig",
 *       // Use _SERVER if FastCGI environment variables are to be used
 *       "env_config" => $_SERVER,
 *   ]);
 *
 * @psalm-type ElementValue string|Array<string, string|array>
 */
class Awardit_Magento_EnvConfig extends Mage_Core_Model_Config {
    const REQUIRED_ENV = [
        "MAGENTO_CRYPT_KEY",
        "RESOURCE_DEFAULT_SETUP",
    ];

    /**
     * @var Awardit_Magento_EnvConfig_Data
     */
    protected $localConfig;

    /**
     * @param mixed $data
     */
    public function __construct($data = null) {
        $env = null;

        if(is_array($data) && array_key_exists("env_config", $data) && is_array($data["env_config"])) {
            $env = $data["env_config"];

            // We don't want to propagate this info to the rest of the application
            unset($data["env_config"]);
        }

        $this->localConfig = new Awardit_Magento_EnvConfig_Data($this->createLocalConfig($env ?: getenv()));

        parent::__construct($data);
    }

    /**
     * @param Array<string, string> $env
     * @return Array<string, ElementValue>
     */
    protected function createLocalConfig(array $env): array {
        foreach(self::REQUIRED_ENV as $envName) {
            if( ! array_key_exists($envName, $env)) {
                throw new Exception(sprintf(
                    "%s: Required environment variable '%s' is not set.",
                    __CLASS__,
                    $envName
                ));
            }
        }

        $resources = [
            "db" => [
                "table_prefix" => "",
            ],
        ];

        foreach($env as $key => $value) {
            if(strpos($key, "RESOURCE_") === 0) {
                $query = [];
                $name = strtolower(substr($key, strlen("RESOURCE_")));
                $dsn = parse_url($value);

                if( ! $dsn ||
                    ! array_key_exists("scheme", $dsn) ||
                    ! array_key_exists("host", $dsn)) {
                    throw new Exception(sprintf(
                        "%s: Malformed DSN in config-variable '%s'.",
                        __CLASS__,
                        $key
                    ));
                }

                if($dsn["scheme"] === "use") {
                    $resources[$name] = [
                        "connection" => [
                            "use" => strtolower(trim(rawurldecode($dsn["host"]), " \t/")),
                        ],
                    ];

                    continue;
                }

                if( ! array_key_exists("path", $dsn)) {
                    throw new Exception(sprintf(
                        "%s: Malformed DSN in config-variable '%s'.",
                        __CLASS__,
                        $key
                    ));
                }

                parse_str($dsn["query"] ?? "", $query);

                $host = rawurldecode($dsn["host"]);

                if(array_key_exists("port", $dsn)) {
                    $host .= ":".$dsn["port"];
                }

                $resources[$name] = [
                    "connection" => [
                        "active" => "1",
                        "model" => "mysql4",
                        "type" => "pdo_".strtolower($dsn["scheme"]),
                        "username" => rawurldecode($dsn["user"] ?? ""),
                        "password" => rawurldecode($dsn["pass"] ?? ""),
                        "host" => $host,
                        "dbname" => trim(rawurldecode($dsn["path"]), "/ "),
                        "initStatements" => rawurldecode($query["initStatements"] ?? "SET NAMES utf8mb4"),
                        "profiler" => $query["profiler"] ?? "0",
                    ],
                ];
            }
        }

        $global = [
            "install" => [
                // TODO: Configurable?
                "date" => gmdate("Y-m-d H:i:s", filemtime(__FILE__)),
            ],
            "crypt" => [
                "key" => $env["MAGENTO_CRYPT_KEY"],
            ],
            "disable_local_modules" => "false",
            "resources" => $resources,
            // TODO: Configurable
            "session_save" => "files",
        ];

        if(array_key_exists("MAGENTO_CACHE_BACKEND", $env)) {
            $global["cache"] = [
                "backend" => $env["MAGENTO_CACHE_BACKEND"],
            ];
        }

        $data = [
            "global" => $global,
            // TODO: Allow disabling admin (not enough to just remove this, got to disable the router)
            // TODO: Disable everything BUT admin if we enable admin?
            "admin" => [
                "routers" => [
                    "adminhtml" => [
                        "args" => [
                            "frontName" => "administration",
                        ],
                    ],
                ],
            ],
        ];

        // Set default/dev/template/allow_symlink=1 if we are not production or staging
        if( ! in_array($env["APP_ENV"] ?? "", ["production", "staging"])) {
            $data["default"] = [
                "dev" => [
                    "template" => [
                        "allow_symlink" => "1",
                    ],
                ],
            ];
        }

        return $data;
    }

    // Replaced to only load config.xml
    public function loadBase(): void {
        $this->_isLocalConfigLoaded = true;

        $this->loadFile(Mage::getBaseDir()."/app/etc/config.xml");
        $this->extend($this->localConfig);
    }

    // Replaced to load local.xml from in-memory
    public function loadModules() {
        Varien_Profiler::start('config/load-modules');
        $this->_loadDeclaredModules();

        $this->loadModulesConfiguration(['config.xml'], $this);
        // Override any module configuration (eg. default database connections)
        $this->extend($this->localConfig);

        $this->applyExtends();
        Varien_Profiler::stop('config/load-modules');
        return $this;
    }
}
