<?php

namespace LakeDrops\Component\Composer;

use Composer\IO\IOInterface;
use Exception;
use Symfony\Component\Dotenv\Dotenv as SymfonyDotenv;

/**
 * Manages .env files.
 */
final class Dotenv {

  /**
   * Name of the plugin "owning" this instance, used as a prefix.
   *
   * @var string
   */
  private $name;

  /**
   * The input-output object of the composer session.
   *
   * @var \Composer\IO\IOInterface
   */
  private $io;

  /**
   * The Symfony Dotenv object.
   *
   * @var \Symfony\Component\Dotenv\Dotenv
   */
  private $dotenv;

  /**
   * Dotenv constructor.
   *
   * @param string $name
   *   Name of the plugin "owning" this instance, used as a prefix.
   * @param \Composer\IO\IOInterface $io
   *   The input-output object of the composer session.
   */
  public function __construct(string $name, IOInterface $io) {
    $this->name = strtoupper($name);
    $this->io = $io;
    $this->dotenv = new SymfonyDotenv();
    $this->load();
  }

  /**
   * Receive a value from the environment with the project name as prefix.
   *
   * First of all, it prefixes the key with the plugin name and converts the
   * result into upper case. Then looking for the value with PHP's getenv and if
   * nothing found, it prompts the user for input if the session is interactive.
   * Finally, if the value is still not available, the default value is being
   * used.
   *
   * @param string $key
   *   The key of the environment variable.
   * @param string $prompt
   *   The text being displayed when prompting the user.
   * @param string $default
   *   The default value.
   *
   * @return string
   *   The value.
   */
  public function receive(string $key, string $prompt, $default = ''): string {
    return $this->receiveGlobal($this->name . '_' . $key, $prompt, $default);
  }

  /**
   * Receive a value from the environment.
   *
   * @param string $key
   *   The key of the environment variable.
   * @param string $prompt
   *   The text being displayed when prompting the user.
   * @param string $default
   *   The default value.
   *
   * @return string
   *   The value.
   */
  public function receiveGlobal(string $key, string $prompt, $default = ''): string {
    $key = strtoupper($key);
    $value = $_ENV[$key] ?? NULL;
    if ($value === NULL) {
      if ($this->io->isInteractive()) {
        $value = $this->io->ask($prompt . ': ');
      }
      if ($value === NULL) {
        $value = $default;
      }
      $this->put($key, $value, TRUE);
    }
    else {
      $this->put($key, $value);
    }

    return $value;
  }

  /**
   * Set an environment variable.
   *
   * Sets the environment variable into a .env file only if there isn't an
   * environment variable present yet. The .env file will also be added to the
   * .gitignore file.
   *
   * @param string $key
   *   The name of the environment variable.
   * @param string $value
   *   The value of the environment variable.
   * @param bool $overwrite
   */
  public function put(string $key, string $value, $overwrite = FALSE): void {
    $data = $this->parse();
    $key = strtoupper($key);
    if (!$overwrite && isset($data[$key])) {
      return;
    }
    $data[$key] = $value;
    $envContent = '';
    foreach ($data as $k => $v) {
      $envContent .= $k . '=' . $v . PHP_EOL;
    }
    file_put_contents('.env', $envContent);
    $this->load();
    Utils::gitIgnore('.env');
  }

  /**
   * Replace environment variables in keys and values of a given array.
   *
   * Credit: https://github.com/composer/composer/pull/5837
   *
   * @param mixed $data
   *   The keyed array in which to replace environment variables.
   *
   * @return array
   *   The same data with environment variables being replaced.
   */
  public function replaceEnvironmentVariables($data): array {
    array_walk_recursive($data, array($this, 'findEnvironmentVariables'));
    return $data;
  }

  /**
   * Identify and replace environment variables in a key pair
   *
   * @param string $item
   * @param string $key
   */
  private function findEnvironmentVariables(string &$item, string &$key): void {
    $pattern = '@\{\$env:(\w)\}@i';
    preg_match_all($pattern, $item, $item_matches);
    preg_match_all($pattern, $key, $key_matches);

    if (!empty($item_matches[1])) {
      $item = $this->replaceItemEnvironmentVariables($item, $item_matches[1]);
    }
    if (!empty($key_matches[1])) {
      $key = $this->replaceItemEnvironmentVariables($key, $key_matches[1]);
    }
  }

  /**
   * Replace environment variables in a string
   *
   * @param string $item
   * @param array $matches
   *
   * @return string
   */
  private function replaceItemEnvironmentVariables(string $item, array $matches): string {
    foreach ($matches as $var) {
      $value = $_ENV[$var] ?? NULL;
      if ($value === NULL) {
        $value = '';
      }
      $item = str_replace( '{$env:' . $var . '}', $value, $item );
    }
    return $item;
  }

  /**
   * Parse the local .env file and return the content as an array.
   *
   * @return array
   */
  private function parse(): array {
    $envContent = file_exists('.env') ? file_get_contents('.env') : '';
    return $this->dotenv->parse($envContent);
  }

  /**
   * Load the local .env file.
   */
  private function load(): void {
    try {
      foreach ($this->parse() as $key => $value) {
        unset($_SERVER[$key]);
      }
      $this->dotenv->load('.env');
    }
    catch (Exception $ex) {
      // Ignore.
    }
  }

}