<?php

namespace Velis\Bpm;

use Exception;
use ReflectionException;
use User\User as SystemUser;
use Velis\App;
use Velis\Arrays;
use Velis\Bpm\Dashboard\Widget;
use Velis\Lang;
use Velis\Model\BaseModel;
use Velis\ParameterBag;

/**
 * Dashboard management model
 *
 * @author Olek Procki <olo@velis.pl>
 * @author Michał Nosek <michal.nosek@velis.pl>
 */
class Dashboard extends BaseModel
{
    /**
     * Dashboard owner
     * @var SystemUser
     */
    protected $_user;


    /**
     * @var array
     */
    protected $fixedWidgets;

    /**
     * Widgets enabled by user
     * @var Widget[]
     */
    protected $_enabledWidgets;


    /**
     * All available widgets for user
     * @var Widget[]
     */
    protected $_availableWidgets;

    /**
     * All available default widgets for user
     * @var Widget[]
     */
    protected $_availableDefaultWidgets;


    /**
     * Dashboard object constructor
     * @param \Velis\Bpm\User|int $data
     */
    public function __construct($data = null)
    {
        if ($data == null) {
            $this->_user = App::$user;
        } else {
            if (!$data instanceof User) {
                $this->_user = SystemUser::get($data);
            } else {
                $this->_user = $data;
            }
        }
    }


    /**
     * Returns all user widgets list
     * @return Widget[]
     */
    public function getWidgets($params = null)
    {
        if (!isset($this->_enabledWidgets)) {
            $query = "SELECT * FROM app.widget_to_user_tab JOIN app.widget_tab w USING(widget_id)
                      WHERE w.is_active = 1 ";

            $params['user_id'] = $this->_user->id();
            $query .= self::$_db->conditions($params);

            $query .= " AND (EXISTS (
                            SELECT 1 FROM acl.user_role_tab ur
                                     JOIN app.widget_role_tab wr
                                     ON ur.role_id = wr.role_id AND ur.user_id = :user_id AND wr.widget_id = w.widget_id
                        ) OR EXISTS(
                            SELECT 1 FROM app.widget_user_tab wu
                                     WHERE wu.user_id = :user_id AND wu.widget_id = w.widget_id
                        ) )";

            $query .= " ORDER BY row_no";

            $this->_enabledWidgets = array();
            $i = 1;

            foreach (self::$_db->getAll($query, $params) as $row) {
                $widget = Widget::get($row['widget_id']);
                $widget->append($row);
                $class = '\Dashboard\Widget\\' . $widget->identifier(true);

                if (class_exists($class)) {
                    $widget = new $class($widget);
                }
                $this->_enabledWidgets[$i++] = clone $widget;
            }
        }
        return $this->_enabledWidgets;
    }

    /**
     * Returns widgets available for user
     * @return Widget[]
     * @throws ReflectionException
     */
    public function getAvailableWidgets($userId = null)
    {
        if (!$userId) {
            $userId = $this->_user->id();
        }

        if (!isset($this->_availableWidgets)) {
            $params = new ParameterBag([
                'user_id' => $userId,
                'is_active' => 1,
            ]);

            $this->_availableWidgets = Widget::listAll($params, 'name_' . Lang::getLanguage());
        }

        return $this->_availableWidgets;
    }

    /**
     * Returns widgets available for user.
     * @return Widget[]
     */
    public function getAvailableDefaultWidgets($userId = null)
    {
        if (!$userId) {
            $userId = $this->_user->id();
        }

        if (!isset($this->_availableDefaultWidgets)) {
            $this->_availableDefaultWidgets = Widget::listAll(['user_id' => $userId, 'is_active' => 1, 'is_default' => 1], 'name_' . Lang::getLanguage());
        }
        return $this->_availableDefaultWidgets;
    }

    /**
     * Returns widgets count for user
     * @return int
     */
    public function getWidgetCount()
    {
        return self::$_db->getOne(
            "SELECT count(*) FROM app.widget_to_user_tab WHERE user_id = :user_id",
            array('user_id' => $this->_user->id())
        );
    }


    /**
     * Gets last column no
     * @return int
     */
    public function getLastColNo()
    {
        return self::$_db->getOne(
            "SELECT COALESCE(MAX(column_no),1) FROM app.widget_to_user_tab WHERE user_id=:user_id",
            array('user_id' => $this->_user->id())
        );
    }


    /**
     * Get last row no
     * @return int
     */
    public function getLastRowNo()
    {
        return self::$_db->getOne(
            "SELECT COALESCE(MAX(row_no),1) FROM app.widget_to_user_tab WHERE user_id=:user_id",
            array('user_id' => $this->_user->id())
        );
    }


    /**
     * Returns true if dashboard has $widget enabled
     *
     * @param type $widget
     * @return bool
     */
    public function has($widget)
    {
        if (!$widget instanceof Widget) {
            $widget = new Widget($widget);
        }
        return $widget->belongs($this->getWidgets());
    }


    /**
     * Retrieves next widget sequence id
     * @return int
     */
    public function getNextSeqenceKey()
    {
        return self::$_db->getOne(
            "SELECT nextval('app.widget_to_user_tab_widget_to_user_id_seq')"
        );
    }

    public function hasDuplicates($formWidgets)
    {
        if (!$formWidgets) {
            return false;
        }

        $widgetsHash = array_map(function ($widget) {
            return md5(implode([$widget['widget_id'], str_replace('null', '', $widget['params'])]));
        }, $formWidgets);

        return count($widgetsHash) !== count(array_unique($widgetsHash));
    }

    /**
     * Saves widgets order
     *
     * @param array $formWidgets
     * @return void
     */
    public function saveWidgets(array $formWidgets): void
    {
        $dbWidgets = self::$_db->getAll(
            "SELECT * FROM app.widget_to_user_tab WHERE user_id = :user_id",
            array('user_id' => $this->_user->id()),
            'widget_to_user_id'
        );
        $widgetParams = [];

        self::$_db->startTrans();

        try {
            if ($this->hasDuplicates($formWidgets)) {
                throw new Exception(Lang::get('GENERAL_DASHBOARD_WIDGET_IDENTICAL_PARAMETERS'));
            }
            foreach ($dbWidgets as $key => $widget) {
                if (!array_key_exists($key, $formWidgets)) {
                    self::$_db->execDML(
                        "DELETE FROM app.widget_to_user_tab WHERE widget_to_user_id = :widget_to_user_id",
                        array('widget_to_user_id' => $key)
                    );
                    unset($dbWidgets[$key]);
                } else {
                    $widgetParams[$key] = $widget['widget_id'] . '-' . md5(serialize($widget['params']));
                }
            }

            $i = 0;

            $allowedWidgets = $this->getAvailableWidgets();

            foreach ($formWidgets as $key => $widget) {
                if ($allowedWidgets[$widget['widget_id']]) {
                    if (array_key_exists($key, $dbWidgets)) {
                        $sql = 'UPDATE app.widget_to_user_tab
                                SET column_no = :column_no,
                                    grid_no = :grid_no,
                                    row_no = :row_no
                                WHERE widget_to_user_id = :widget_to_user_id';

                        $params = [
                            'column_no' => $widget['column_no'],
                            'grid_no' => $widget['grid_no'],
                            'row_no' => $i,
                            'widget_to_user_id' => $key
                        ];

                        self::$_db->execDML($sql, $params);
                    } else {
                        $params = [
                            'widget_to_user_id' => $key,
                            'user_id' => $this->_user->id(),
                            'widget_id' => $widget['widget_id'],
                            'column_no' => $widget['column_no'],
                            'row_no' => $i,
                            'params' => $widget['params'],
                            'grid_no' => $widget['grid_no']
                        ];

                        $widgetParams[$key] = $widget['widget_id'] . '-' . md5(serialize($widget['params'] ?: null));

                        self::$_db->insert('app.widget_to_user_tab', $params);
                    }
                    $i++;
                }
            }

            if (count($widgetParams) != count(array_unique($widgetParams))) {
                throw new Exception(Lang::get('GENERAL_DASHBOARD_WIDGET_DUPLICATION'));
            }

            self::$_db->commit();

            $response = [
                'message' => Lang::get('GENERAL_DATA_SAVED')
            ];
        } catch (Exception $e) {
            self::$_db->rollback();

            $response = [
                'error' => $e->getMessage()
            ];
        }

        echo json_encode($response);
    }


    /**
     * Sets default widgets for user
     * @throws \Exception
     */
    public function applyDefaultWidgets()
    {
        try {
            if ($this->getWidgetCount() == 0) {
                self::$_db->execDML(
                    "DELETE FROM app.widget_to_user_tab wut
                     WHERE wut.user_id = :user_id
                     AND (
                        SELECT is_default FROM app.widget_tab w
                        WHERE user_id = wut.user_id AND w.widget_id = wut.widget_id
                     ) = 1",
                    array('user_id' => $this->_user->id())
                );

                $all = $this->getAvailableWidgets();

                $colno = $this->getLastColNo();
                $rowno = $this->getLastRowNo() + 1;

                if ($colno >= 3) {
                    $colno = 1;
                }

                $i = 1;


                foreach ($all as $widget) {
                    $data = array(
                        'user_id'   => $this->_user->id(),
                        'widget_id' => $widget['widget_id'],
                        'column_no' => $colno,
                        'row_no'    => $rowno
                    );

                    self::$_db->insert('app.widget_to_user_tab', $data);

                    $colno += 1;

                    if ($i % 3 == 0) {
                        $rowno += 1;
                        $colno = 1;
                    }
                    $i++;
                }
            }
        } catch (Exception $e) {
            throw new Exception(Lang::get('GENERAL_DEFAULT_WIDGET_ADD_ERROR'), 23509, $e);
        }
    }

    /**
     * @return array
     */
    protected function loadFixedWidgets(): array
    {
        if (!$this->fixedWidgets) {
            $query = "SELECT wf.name, wf.widget_place, wf.widget_fixed_id, wf.visible "
                . "FROM app.widget_fixed_tab wf "
                . "WHERE wf.user_id = :user_id ORDER BY wf.widget_place";

            $this->fixedWidgets = self::$_db->getAll($query, ['user_id' => App::$user->id()]);
        }
        return $this->fixedWidgets;
    }


    /**
     * @return bool
     */
    public function hasFixedWidgets(): bool
    {
        return !empty($this->loadFixedWidgets());
    }

    /**
     * Get fixed widget
     * @param bool $visible
     * @return array
     */
    public function getFixedWidgets($visible = true): array
    {
        $widgets = $this->loadFixedWidgets();
        $filter = $visible ? 1 : 0;
        return array_values(Arrays::byValue($widgets, 'visible', $filter));
    }

    /**
     * Set fixed widget
     * @param array $widgets
     */
    public function setFixedWidgets($widgets)
    {
        $this->fixedWidgets = $widgets;
    }

    public static function changeFixedWidgetPlace($widget, $place)
    {
        $query = "SELECT * "
               . "FROM app.widget_fixed_tab wf "
               . "WHERE wf.widget_fixed_id = :widget AND wf.user_id = :user_id";

        $oldWidget = reset(self::$_db->getAll($query, array('widget' => $widget, 'user_id' => App::$user->id())));

        $query = "SELECT * "
               . "FROM app.widget_fixed_tab wf "
               . "WHERE wf.widget_place = :place AND wf.user_id = :user_id";

        $changedWidget = reset(self::$_db->getAll($query, array('place' => $place, 'user_id' => App::$user->id())));

        if ($oldWidget['widget_fixed_id'] != $changedWidget['widget_fixed_id']) {
            self::$_db->execDML(
                "UPDATE app.widget_fixed_tab
                        SET widget_place = :place"
                        . ((!$oldWidget['visible']) ? ", visible = 1 " : " ") .
                        "WHERE user_id = :user_id AND widget_fixed_id = :widget",
                array(
                           'place'   => $place,
                           'user_id' => App::$user->id(),
                           'widget'  => $widget
                       )
            );

            self::$_db->execDML(
                "UPDATE app.widget_fixed_tab
                        SET widget_place = :place"
                        . ((!$oldWidget['visible']) ? ", visible = 0 " : " ") .
                        "WHERE user_id = :user_id AND widget_fixed_id = :widget",
                array(
                           'place'   => $oldWidget['widget_place'],
                           'user_id' => App::$user->id(),
                           'widget'  => $changedWidget['widget_fixed_id']
                       )
            );
        }
    }


    /**
     * Change fixed widget visibility
     * @param int $widget
     * @param bool $visible
     */
    public static function changeFixedWidgetVisibility($widget, $visible)
    {
        self::$_db->execDML(
            "UPDATE app.widget_fixed_tab
                    SET visible = :visible
                    WHERE user_id = :user_id AND widget_fixed_id = :widget",
            [
               'visible' => ($visible) ? '1' : '0',
               'user_id' => App::$user->id(),
               'widget' => $widget
            ]
        );

        // fix sorting numbers after unpining widget
        self::$_db->execDML(
            "UPDATE app.widget_fixed_tab wf
                    SET widget_place = wf2.new_widget_place
                    FROM (SELECT wf3.widget_fixed_id,
                            row_number() over (ORDER BY wf3.visible DESC, wf3.widget_place ASC) AS new_widget_place
                            FROM app.widget_fixed_tab wf3
                            WHERE user_id = :user_id
                            ORDER BY wf3.visible DESC, wf3.widget_place ASC) AS wf2(widget_fixed_id, new_widget_place)
                    WHERE user_id = :user_id
                        AND wf.widget_fixed_id = wf2.widget_fixed_id",
            [
               'user_id' => App::$user->id(),
            ]
        );
    }
}
