<?php

namespace models;

use Slim\Container;

/**
 * Class SettingsModel
 * @package models
 */
class SettingsModel implements ISettings
{
    const REQUIRED_RULE_NAME = 'required';
    /**
     * @var array
     */
    private $allowedKeys = [
        'domain_controller' => 'string|' . self::REQUIRED_RULE_NAME,
        'base_dn' => 'string|' . self::REQUIRED_RULE_NAME,
        'login_attribute' => 'string|' . self::REQUIRED_RULE_NAME,
        'group_attribute' => 'string',
        'port' => 'int',
        'use_ssl' => 'bool',
        'use_tls' => 'bool',
        'timeout' => 'int',
        'ldap_filter' => 'string',
        'anonymous_bind' => 'bool',
        'admin_username' => 'string',
        'admin_password' => 'string'
    ];

    /**
     * @var Container
     */
    private $container;

    /**
     * SettingsModel constructor.
     * @param Container $container
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
    }


    /**
     * @param array $data
     *
     * @return mixed|void
     * @throws \Exception
     */
    public function validate(array $data)
    {
        $errors = [];
        $this->validateExistingData($data, $errors);

        if (sizeof($errors) > 0) {
            throw new \Exception(implode('  ', $errors));
        }

        return true;
    }

    /**
     * @param $data
     * @param $errors
     */
    private function validateExistingData($data, &$errors)
    {
        foreach ($data as $key => $value) {
            if (key_exists($key, $this->allowedKeys)) {
                foreach (explode('|', $this->allowedKeys[$key]) as $rule) {
                    $key = ucfirst($key);
                    switch ($rule) {
                        case 'int':
                            if (!is_numeric($value)) {
                                $errors[] = $key . ' must be integer.';
                            }
                            break;
                        case 'bool':
                            if ($value != 'true' && $value != 'false') {
                                $errors[] = $key . ' must be `true` or `false`.';
                            }
                            break;
                        case 'required':
                            if ($value == '') {
                                $errors[] = $key . ' field is required.';
                            }
                            break;
                    }
                }
            }
        }
    }

    /**
     * @param $data
     * @param $errors
     */
    private function validateRequiredFields($data, &$errors)
    {
        foreach ($this->allowedKeys as $key => $value) {
            $rules = explode('|', $value);
            if (in_array(self::REQUIRED_RULE_NAME, $rules) && (empty($data[$key]) || !isset($data[$key]))) {
                $errors[] = ucfirst($key) . ' field is required.';
            }
        }
    }

    /**
     * @return array|mixed
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function getAll()
    {
        $settings = $this->container->get('ldap');
        $settings['domain_controllers'] = reset($settings['domain_controllers']);
        return $settings;
    }


    /**
     * @return array|mixed
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function getSettingsWithoutPassword()
    {
        $settings = $this->getAll();
        $settings['admin_password'] = $settings['admin_password'] == '' ? 'Password is not set' : 'Password is set';
        $settings['domain_controller'] = $settings['domain_controllers'];
        unset($settings['domain_controllers']);
        return $settings;
    }

    /**
     * @param array $data
     * @return bool
     */
    public function update(array $data)
    {
        $logger = $this->container->logger;
        $logger->debug("SettingsModel::update()", $data);

        $dataToUpdate = $this->prepareData($data);
        $logger->debug("SettingsModel::update(), dataToUpdate=", $dataToUpdate);

        return $this->writeConfig($dataToUpdate);
    }

    /**
     * @param $data
     * @return array
     */
    private function prepareData($data)
    {
        $oldSettings = $this->getAll();
        foreach ($oldSettings as $key => $value) {
            if (is_bool($value)) {
                $oldSettings[$key] = $value ? 'true' : 'false';
            }
        }
        if (isset($data['domain_controller'])) {
            $data['domain_controllers'] = $data['domain_controller'];
            unset($data['domain_controller']);
        }
        return array_merge($oldSettings, $data);
    }

    /**
     * @param $newSettings
     * @return bool
     */
    private function writeConfig($newSettings)
    {
        $config = [
            'domain_controllers' => [$newSettings['domain_controllers']],
            'base_dn' => $newSettings['base_dn'],
            'login_attribute' => $newSettings['login_attribute'],
            'port' => $newSettings['port'],
            'use_ssl' => is_string($newSettings['use_ssl']) ? $newSettings['use_ssl'] === 'true' : $newSettings['use_ssl'],
            'use_tls' => is_string($newSettings['use_tls']) ? $newSettings['use_tls'] === 'true' : $newSettings['use_tls'],
            'timeout' => $newSettings['timeout'],
            'ldap_filter' => $newSettings['ldap_filter'],
            'anonymous_bind' => is_string($newSettings['anonymous_bind']) ? $newSettings['anonymous_bind'] === 'true' : $newSettings['anonymous_bind'],
            'admin_username' => $newSettings['admin_username'],
            'admin_password' => $newSettings['admin_password'],
            'version' => $newSettings['version'],
            'group_attribute' => $newSettings['group_attribute'],
            // 24582 => 1 equals LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
            // current php 5.6 version doesn't support constants like LDAP_OPT_X_TLS_REQUIRE_CERT,
            // only from 7.1 version.
            'custom_options' => [24582 => 3],
        ];

        $fileContent = '<?php return ' . var_export($config, true) . ';';
        $this->container->logger->debug("SettingsModel::writeConfig()", array('$newSettingsContent', $fileContent));
        return (boolean) file_put_contents(CONFIG_PATH . '/settings.ldap.php', $fileContent);
    }

    /**
     * @return bool
     */
    public function makeBackup()
    {
        return (boolean)copy(CONFIG_PATH . '/settings.ldap.php',
            CONFIG_PATH . '/config_dump/settings.ldap.php.' . date('Y-m-d_H:i:s'));
    }
}
