<?php
namespace nur\config;

use Exception;
use nur\A;
use nur\str;
use Throwable;

/**
 * Class EnvConfig: configuration extraite depuis les variables d'environnement
 *
 * les variables doivent être de la forme {PREFIX}_{PROFILE}_{PKEY} où:
 * - PREFIX vaut CONFIG, FILE_CONFIG, JSON_CONFIG ou JSON_FILE_CONFIG
 * - PROFILE est le profil dans lequel la configuration est valide. Le nom du
 *   profil par défaut est dépendant de l'implémentation de IConfigManager
 *   utilisée. Dans l'implémentation actuelle, 'ALL' est le profil par défaut.
 * - PKEY est le chemin de clé dans lequel les caractères '.' sont remplacés
 *   par '__' et '-' par '_' (celà signifie qu'il n'est pas possible de définir
 *   un chemin de clé qui contient le caractère '_')
 *
 * par exemple, la valeur dbs.my-auth.type du profil par défaut est pris dans
 * la variable 'CONFIG_ALL_dbs__my_auth__type'. pour le profil prod c'est la
 * variable 'CONFIG_prod_dbs__my_auth__type'
 *
 * pour représenter le tableau suivant:
 *   [ "type" => "mysql", "name" => "mysql:host=authdb;dbname=auth;charset=utf8",
 *     "user" => "auth_int", "pass" => "auth" ]
 * situé au chemin de clé dbs.auth dans le profil prod, on peut par exemple
 * définir les variables suivantes:
 *   CONFIG_prod_dbs__auth__type="mysql"
 *   CONFIG_prod_dbs__auth__name="mysql:host=authdb;dbname=auth;charset=utf8"
 *   CONFIG_prod_dbs__auth__user="auth_int"
 *   CONFIG_prod_dbs__auth__pass="auth"
 * ou alternativement:
 *   JSON_CONFIG_prod_dbs__auth='{"type":"mysql","name":"mysql:host=authdb;dbname=auth;charset=utf8","user":"auth_int","pass":"auth"}'
 *
 * Les préfixes supportés sont, dans l'ordre de précédence:
 * - JSON_FILE_CONFIG -- une valeur au format JSON inscrite dans un fichier
 * - JSON_CONFIG -- une valeur au format JSON
 * - FILE_CONFIG -- une valeur inscrite dans un fichier
 * - CONFIG -- une valeur scalaire
 */
class EnvConfig extends DynConfig {
  /** @var array */
  protected $config;

  /** analyser $name et retourner [$pkey, $profile] */
  private static function parse_pkey_profile($name): array {
    $i = strpos($name, "_");
    if ($i === false) return [false, false];
    $profile = substr($name, 0, $i);
    $name = substr($name, $i + 1);
    $pkey = str_replace("__", ".", $name);
    $pkey = str_replace("_", "-", $pkey);
    return [$pkey, $profile];
  }

  private static function invalid_json_data(string $pkey, string $profile, ?string $prefix, ?Throwable $e): ConfigException {
    if ($prefix !== null) $prefix = "$prefix: ";
    return new ConfigException("${prefix}invalid json data for $profile:$pkey", 0, $e);
  }

  /** charger la configuration depuis l'environnement */
  protected function ensureConfigLoaded() {
    if ($this->config !== null) return;

    $config = [];
    $json_files = [];
    $jsons = [];
    $files = [];
    $vars = [];
    foreach (getenv() as $name => $value) {
      if (str::starts_with("JSON_FILE_CONFIG_", $name)) {
        $json_files[str::without_prefix("JSON_FILE_CONFIG_", $name)] = $value;
      } elseif (str::starts_with("JSON_CONFIG_", $name)) {
        $jsons[str::without_prefix("JSON_CONFIG_", $name)] = $value;
      } elseif (str::starts_with("FILE_CONFIG_", $name)) {
        $files[str::without_prefix("FILE_CONFIG_", $name)] = $value;
      } elseif (str::starts_with("CONFIG_", $name)) {
        $vars[str::without_prefix("CONFIG_", $name)] = $value;
      }
    }
    foreach ($json_files as $name => $file) {
      [$pkey, $profile] = self::parse_pkey_profile($name);
      $data = file_get_contents($file);
      if ($data === false) {
        throw new ConfigException("$file: unable to read json file for $profile:$pkey");
      }
      try {
        $value = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
      } catch (Exception $e) {
        throw self::invalid_json_data($pkey, $profile, $file, $e);
      }
      A::pset($config, "$profile.$pkey", $value);
    }
    foreach ($jsons as $name => $data) {
      [$pkey, $profile] = self::parse_pkey_profile($name);
      try {
        $value = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
      } catch (Exception $e) {
        throw self::invalid_json_data($pkey, $profile, null, $e);
      }
      A::pset($config, "$profile.$pkey", $value);
    }
    foreach ($files as $name => $file) {
      [$pkey, $profile] = self::parse_pkey_profile($name);
      $value = file_get_contents($file);
      if ($value === false) {
        throw new ConfigException("$file: unable to read file for $profile:$pkey");
      }
      A::pset($config, "$profile.$pkey", $value);
    }
    foreach ($vars as $name => $value) {
      [$pkey, $profile] = self::parse_pkey_profile($name);
      A::pset($config, "$profile.$pkey", $value);
    }
    $this->config = $config;
  }

  function has(string $pkey, string $profile): bool {
    $this->ensureConfigLoaded();
    return A::phas($this->config, "$profile.$pkey");
  }

  function get(string $pkey, string $profile) {
    $this->ensureConfigLoaded();
    return A::pget($this->config, "$profile.$pkey");
  }

  function set(string $pkey, $value, string $profile): void {
    $this->ensureConfigLoaded();
    A::pset($this->config, "$profile.$pkey", $value);
  }
}