<?php

namespace Velis\App;

use Exception;
use Velis\App;
use Velis\Arrays;
use Velis\Lang;
use Velis\Model\Cacheable;
use Velis\Model\DataObject;
use Velis\User as SystemUser;

/**
 * Application settings element
 * @author Olek Procki <olo@velis.pl>
 */
class Setting extends DataObject implements Cacheable
{
    /**
     * Null value equivalent
     */
    const NULL_VALUE = '-';


    /**
     * Default order
     * @var string
     */
    protected static $_listDefaultOrder = 'name_pl, setting_id';


    /**
     * Available options
     * @var array
     */
    protected $_options;


    /**
     * @return string
     */
    protected function _getTableName()
    {
        return 'app.setting_tab';
    }


    /**
     * @param SystemUser|int $user
     * @return array
     */
    public static function getUserSettings($user)
    {
        $params = [
            'user_id' => $user instanceof SystemUser ? $user->id() : $user,
        ];

        $query = "SELECT setting_id, value FROM app.setting_tab JOIN app.setting_value_tab
                    USING(setting_id)
                    WHERE user_id=:user_id

                  UNION

                  SELECT s.setting_id, s.default_value FROM app.setting_tab s
                      WHERE application_setting=0
                      AND NOT EXISTS (
                         SELECT 1 FROM app.setting_value_tab sv WHERE sv.user_id=:user_id AND s.setting_id = sv.setting_id
                      )";

        $settings = [];
        foreach (self::$_db->cacheGetAll($query, $params) as $row) {
            $settings[$row['setting_id']] = $row['value'];
        }

        return $settings;
    }


    /**
     * Saves user settings
     *
     * @param array $settings
     * @param SystemUser $user
     *
     * @throws Exception
     */
    public static function setUserSettings($settings, $user)
    {
        self::$_db->startTrans();

        try {
            foreach ($settings as $setting => $value) {
                $params = array (
                    'setting_id' => $setting,
                    'user_id'    => $user->id(),
                    'value'      => $value,
                );
                if (strlen(trim($value))) {
                    self::$_db->insert('app.setting_value_tab', $params);
                }
            }
        } catch (Exception $e) {
            self::$_db->rollback();
            throw $e;
        }
        self::$_db->commit();

        return $settings;
    }


    /**
     * Returns application settings
     * @param bool $forMobile
     * @return array
     */
    public static function getAppSettings($forMobile = false)
    {
        $additionalFields = '';

        if (self::hasField('setting_section_id')) {
            $additionalFields = ", setting_section_id";
        }

        if ($forMobile) {
            $additionalFields = '';
            $updateDate = ", update_date";
            $defaultDate = ", NULL";
        } else {
            $updateDate = '';
            $defaultDate = '';
        }

        $query = "SELECT setting_id, value $additionalFields $updateDate FROM app.setting_tab JOIN app.setting_value_tab
                    USING(setting_id)
                    WHERE user_id IS NULL ";

        if ($forMobile) {
            $query .= " AND include_mobile = 1::SMALLINT ";
        }

        $query .= " UNION

                  SELECT s.setting_id, s.default_value $additionalFields $defaultDate FROM app.setting_tab s
                      WHERE application_setting=1
                      AND NOT EXISTS (
                         SELECT 1 FROM app.setting_value_tab sv WHERE sv.user_id IS NULL AND s.setting_id = sv.setting_id
                      )";
        if ($forMobile) {
            $query .= " AND include_mobile = 1::SMALLINT ";
        }
        $settings = array();
        foreach (self::$_db->getAll($query) as $row) {
            if (isset($row['setting_section_id'])) {
                $settings[$row['setting_section_id']][$row['setting_id']] = $row['value'];
            } else if ($forMobile) {
                $settings[$row['setting_id']] = ['setting_id' => $row['setting_id'], 'value' => $row['value'], 'update_date' => $row['update_date']];
            } else {
                $settings[$row['setting_id']] = $row['value'];
            }
        }

        return $settings;
    }


    /**
     * Saves application settings
     *
     * @param array $settings
     * @throws Exception
     */
    public static function saveAppSettings($settings)
    {
        self::$_db->startTrans();

        self::prepareSettingValueChange();


        try {
            $sql = "
                DELETE FROM app.setting_value_tab
                WHERE 1=1
            ";

            $sql .= self::addAppSpecificConditions();

            self::$_db->execDML($sql);

            foreach ($settings as $setting => $value) {
                $params = [
                    'setting_id' => $setting,
                    'value' => $value,
                ];
                if (strlen(trim($value))) {
                    self::$_db->insert('app.setting_value_tab', $params);
                }
            }
        } catch (Exception $e) {
            self::$_db->rollback();
            throw $e;
        }

        self::executeSettingValueChange();


        self::$_db->commit();
        unset(App::$cache['settings']);
    }

    protected static function prepareSettingValueChange()
    {
        if (!self::$_db->tableExists('app.setting_log_tab')) {
            return;
        }

        $sql = "SELECT * FROM app.setting_value_tab WHERE 1=1";
        $sql .= self::addAppSpecificConditions();

        self::$_db->execDML(
            "CREATE TEMPORARY TABLE setting_value_current_tab
             AS $sql"
        );
    }

    protected static function executeSettingValueChange()
    {
        if (!self::$_db->tableExists('app.setting_log_tab')) {
            return;
        }

        self::logSettingValueChange();
        self::$_db->execDML("DROP TABLE setting_value_current_tab");
    }

    private static function logSettingValueChange()
    {
        $fields = [
            'invoked_by_user_id',
            'user_id',
            'setting_id',
            'app_module_id',
            'value_before_change',
            'value_after_change',
        ];

        $sql = "
            INSERT INTO app.setting_log_tab(" . implode(',', $fields) . ")
                SELECT :invoked_by_user_id,
                    :user_id, 
                    sv.setting_id, 
                    COALESCE(st.app_module_id, ''),
                    svct.value AS value_before_change, 
                    sv.value AS value_after_change 
                FROM app.setting_value_tab sv 
                JOIN setting_value_current_tab svct ON sv.setting_id = svct.setting_id
                JOIN app.setting_tab st ON st.setting_id = sv.setting_id
                WHERE COALESCE(sv.value, '') != COALESCE(svct.value, '') AND sv.user_id IS NULL
            ";

        $params['user_id'] = App::$user->id();
        $params['invoked_by_user_id'] = self::getInvokedUserId();

        self::$_db->execDML($sql, $params);
    }

    private static function getInvokedUserId(): int
    {
        if (App::$user->isSwitched()) {
            return App::$user->getSourceUser()['user_id'];
        }

        return App::$user->id();
    }


    protected static function addAppSpecificConditions(): string
    {
        $sql = "
                AND user_id IS NULL
                AND EXISTS (
                    SELECT 1 FROM app.setting_tab s
                    WHERE
                        s.setting_id = app.setting_value_tab.setting_id
                        " . (self::hasField('visible') ? "AND s.visible = 1" : "") . "
                )
        ";

        // do not erase internal settings unless invoked by super user
        if (!App::isSuper()) {
            $sql .= "
                    AND EXISTS (
                        SELECT 1 FROM app.setting_tab s
                        WHERE
                            s.setting_id=app.setting_value_tab.setting_id
                            AND s.is_internal=0
                    )
                ";
        }

        return $sql;
    }

    /**
     * Returns setting label
     * @return string
     */
    public function getName()
    {
        return $this->getTranslatedName('name');
    }


    /**
     * Saves setting value
     *
     * @param string $value
     * @param SystemUser|int|null $user
     * @return Setting
     * @throws Exception
     */
    public function saveValue($value, $user = null)
    {
        if (!$user) {
            $user = App::$user;
        }
        $userId = $user instanceof SystemUser ? $user->id() : $user;

        $commit = self::$_db->startTrans();

        if ($this->application_setting) {
            self::$_db->execDML(
                "DELETE FROM app.setting_value_tab WHERE setting_id = :setting_id",
                $this->_getPrimaryKeyParam()
            );
            self::$_db->insert('app.setting_value_tab', [
                'setting_id' => $this->id(),
                'value'      => $value
            ]);
        } else {
            self::$_db->execDML("DELETE FROM app.setting_value_tab WHERE setting_id = :setting_id AND user_id = :user_id", [
                'setting_id' => $this->id(),
                'user_id'    => $userId
            ]);
            self::$_db->insert('app.setting_value_tab', [
                'setting_id' => $this->id(),
                'value'      => $value,
                'user_id'    => $userId
            ]);
        }

        if ($commit) {
            self::$_db->commit();
        }
        if ($this->application_setting) {
            unset(App::$cache['settings']);
        }

        return $this;
    }


    /**
     * Returns setting default value
     * @return string
     */
    public function getDefault()
    {
        return $this['default_value'];
    }


    /**
     * Returns available options for setting
     * @param bool $reload
     * @return array
     */
    public function getOptions($reload = false)
    {
        if (!isset($this->_options) || $reload) {
            $this->_options = array();

            if ($this['range']) {
                list ($start,$end) = explode('-', $this['range']);
                $options = [];

                for ($i = $start; $i <= $end; $i++) {
                    $options[$i] = $i;
                    $this->_options[$i] = $i;
                }
                $this->_options = $options;
            } else {
                 self::loadItemsOptions(array($this));
            }
        }

        return $this->_options;
    }


    /**
     * Loads options for settings
     *
     * @param Setting[] $settings
     * @return Setting[]
     */
    public static function loadItemsOptions($settings)
    {
        $settingIds = self::getCollectionIds($settings);

        if ($settingIds) {
            foreach ($settings as $setting) {
                $setting->_options = array();
            }
            $query = "SELECT * FROM app.setting_allowed_value_tab WHERE setting_id IN('" . implode("','", $settingIds) . "') ORDER BY setting_id, value";

            foreach (self::$_db->getAll($query) as $row) {
                if (!isset($row['label'])) {
                    $row['label'] = $row['value'];
                }
                if (!isset($setting) || $setting['setting_id'] != $row['setting_id']) {
                    $setting = Arrays::find($settings, 'setting_id', $row['setting_id']);
                }
                $setting->_options[$row['value']] = isset($row['label_' . Lang::getLanguage()]) ? $row['label_' . Lang::getLanguage()] : $row['label'];
            }

            foreach ($settings as $setting) {
                if (empty($setting->_options) && $setting['range']) {
                    $setting->getOptions(true);
                }
            }
        }

        return $settings;
    }


    /**
     * Returns only settings when field visible is enabled
     * @return Setting[]
     */
    public static function listVisible(bool $onlyNonInternal = false)
    {
        /** @var Setting[] $settings */
        $settings = self::listCached();

        foreach ($settings as $key => $val) {
            if ($val['visible'] == 0) {
                unset($settings[$key]);
            }

            if ($onlyNonInternal && $val['is_internal'] == 1) {
                unset($settings[$key]);
            }
        }

        return $settings;
    }
}
