<?php
/**
 * User model class.
 * Class also responsible for dates conversion and representation.
 */

namespace App;

use App\Email;
use App\Events\UserDeleted;
use App\Follower;
use App\Mail\PasswordChanged;
use App\Mail\UserInvite;
use App\Notifications\WebsiteNotification;
use Carbon\Carbon;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Watson\Rememberable\Rememberable;

class User extends Authenticatable
{
    use Notifiable;
    use Rememberable;

    public $rememberCacheDriver = 'array';

    const PHOTO_DIRECTORY = 'users';
    const PHOTO_QUALITY = 77;

    const EMAIL_MAX_LENGTH = 100;

    const EMAIL_DELETED_SUFFIX = '_deleted';

    const DEFAULT_TIMEZONE = 'UTC';

    /**
     * Roles.
     */
    const ROLE_USER = 1;
    const ROLE_ADMIN = 2;

    public static $roles = [
        self::ROLE_ADMIN => 'admin',
        self::ROLE_USER  => 'user',
    ];

    /**
     * Types.
     */
    const TYPE_USER = 1;
    const TYPE_ROBOT = 2; // Workflows, teams, etc.

    /**
     * Statuses.
     */
    const STATUS_ACTIVE = 1;
    const STATUS_DISABLED = 2;
    const STATUS_DELETED = 3;

    /**
     * Invite states.
     */
    const INVITE_STATE_ACTIVATED = 1;
    const INVITE_STATE_SENT = 2;
    const INVITE_STATE_NOT_INVITED = 3;

    /**
     * For how long invitation link is valid.
     */
    const INVITE_TTL_DAYS = 7;

    /**
     * Time formats.
     */
    const TIME_FORMAT_12 = 1;
    const TIME_FORMAT_24 = 2;

    /**
     * Global user permissions.
     */
    const PERM_DELETE_CONVERSATIONS = 1;
    const PERM_EDIT_CONVERSATIONS   = 2;
    const PERM_EDIT_SAVED_REPLIES   = 3;
    const PERM_EDIT_TAGS            = 4;
    const PERM_EDIT_CUSTOM_FOLDERS  = 5;
    const PERM_EDIT_USERS           = 10;

    public static $user_permissions = [
        self::PERM_DELETE_CONVERSATIONS,
        self::PERM_EDIT_CONVERSATIONS,
        self::PERM_EDIT_SAVED_REPLIES,
        self::PERM_EDIT_TAGS,
        self::PERM_EDIT_CUSTOM_FOLDERS,
        self::PERM_EDIT_USERS,
    ];

    const WEBSITE_NOTIFICATIONS_PAGE_SIZE = 25;
    const WEBSITE_NOTIFICATIONS_PAGE_PARAM = 'wp_page';

    /**
     * The attributes that are not mass assignable.
     *
     * @var array
     */
    protected $guarded = ['role'];

    /**
     * The attributes that should be hidden for arrays, excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * Attributes fillable using fill() method.
     *
     * @var [type]
     */
    protected $fillable = ['status', 'first_name', 'last_name', 'email', 'password', 'timezone', 'photo_url', 'type', 'emails', 'job_title', 'phone', 'time_format', 'locale'];

    protected $casts = [
        'permissions' => 'array',
    ];
    
    public function __construct(array $attributes = array())
    {
        $this->setRawAttributes(array_merge($this->attributes, array(
            'timezone' => config('app.timezone') ?: User::DEFAULT_TIMEZONE,
        )), true);
        parent::__construct($attributes);
    }

    /**
     * For array_unique function.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->id.'';
    }

    /**
     * Get mailboxes to which usre has access.
     */
    public function mailboxes()
    {
        return $this->belongsToMany('App\Mailbox');
    }

    /**
     * Cached mailboxes.
     */
    public function mailboxes_cached()
    {
        return $this->mailboxes()->rememberForever();
    }

    public function mailboxesWithSettings()
    {
        return $this->belongsToMany('App\Mailbox')->as('settings')
            ->withPivot('after_send')
            ->withPivot('hide')
            ->withPivot('mute')
            ->withPivot('access');
    }

    /**
     * Get conversations assigned to user.
     */
    public function conversations()
    {
        return $this->hasMany('App\Conversation');
    }

    /**
     * User's folders.
     */
    public function folders()
    {
        return $this->hasMany('App\Folder');
    }

    /**
     * User's subscriptions.
     */
    public function subscriptions()
    {
        return $this->hasMany('App\Subscription');
    }

    /**
     * Get user role.
     *
     * @return string
     */
    public function getRoleName($ucfirst = false)
    {
        $role_name = self::$roles[$this->role];
        if ($ucfirst) {
            $role_name = ucfirst($role_name);
        }

        return $role_name;
    }

    /**
     * Check if user is admin.
     *
     * @return bool
     */
    public function isAdmin()
    {
        return $this->role == self::ROLE_ADMIN;
    }

    /**
     * Get user full name.
     *
     * @return string
     */
    public function getFullName()
    {
        return \Eventy::filter('user.full_name', $this->first_name.' '.$this->last_name, $this);
    }

    /**
     * Get user first name.
     *
     * @return string
     */
    public function getFirstName()
    {
        return $this->first_name;
    }

    /**
     * Get mailboxes to which user has access.
     */
    public function mailboxesCanView($cache = false)
    {
        if ($this->isAdmin()) {
            if ($cache) {
                $mailboxes = Mailbox::rememberForever()->get();
            } else {
                $mailboxes = Mailbox::all();
            }
        } else {
            if ($cache) {
                $mailboxes = $this->mailboxes_cached;
            } else {
                $mailboxes = $this->mailboxes;
            }
        }

        return $mailboxes->sortBy('name');
    }

    /**
     * Get mailboxes to which user has access.
     */
    public function mailboxesCanViewWithSettings($cache = false)
    {
        $user = $this;

        if ($this->isAdmin()) {
            $query = Mailbox::select(['mailboxes.*', 'mailbox_user.hide', 'mailbox_user.mute', 'mailbox_user.access'])
                        ->leftJoin('mailbox_user', function ($join) use ($user) {
                            $join->on('mailbox_user.mailbox_id', '=', 'mailboxes.id');
                            $join->where('mailbox_user.user_id', $user->id);
                        });
        } else {
            $query = Mailbox::select(['mailboxes.*', 'mailbox_user.hide', 'mailbox_user.mute', 'mailbox_user.access'])
                        ->join('mailbox_user', function ($join) use ($user) {
                            $join->on('mailbox_user.mailbox_id', '=', 'mailboxes.id');
                            $join->where('mailbox_user.user_id', $user->id);
                        });
        }
        if ($cache) {
            return $query->rememberForever()->get();
        } else {
            return $query->get();
        }
    }

    public function mailboxesSettings($cache = true)
    {
        $user = $this;

        $query = MailboxUser::where('user_id', $user->id);

        if ($cache) {
            return $query->rememberForever()->get();
        } else {
            return $query->get();
        }
    }

    public function mailboxSettings($mailbox_id)
    {
        $settings = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first();

        if (!$settings) {
            return Mailbox::getDummySettings();
        }

        return $settings;
    }

    /**
     * Get IDs of mailboxes to which user has access.
     */
    public function mailboxesIdsCanView()
    {
        if ($this->isAdmin()) {
            return Mailbox::pluck('id')->toArray();
        } else {
            return $this->mailboxes()->pluck('mailboxes.id')->toArray();
        }
    }

    public function hasAccessToMailbox($mailbox_id)
    {
        $ids = $this->mailboxesIdsCanView();
        return in_array($mailbox_id, $ids);
    }

    /**
     * Check to see if the user can manage any mailboxes
     */
    public function hasManageMailboxAccess()
    {
        if ($this->isAdmin()) {
            return true;
        } else {
            //$mailboxes = $this->mailboxesCanViewWithSettings(true);
            $mailboxes = $this->mailboxesSettings();
            foreach ($mailboxes as $mailbox) {
                if (!empty($mailbox->access) && !empty(json_decode($mailbox->access))) {
                    return true;
                }
            };
        }
    }

    /**
     * Check to see if the user can manage a specific mailbox
     */
    public function canManageMailbox($mailbox_id)
    {
        if ($this->isAdmin()) {
            return true;
        } else {
            //$mailbox = $this->mailboxesCanViewWithSettings(true)->where('id', $mailbox_id)->first();
            $mailbox = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first();
            if ($mailbox && !empty(json_decode($mailbox->access ?? ''))) {
                return true;
            }
        }
    }

    /**
     * Main function to check if user has some exta access permission
     * for a given mailbox.
     */
    public function hasManageMailboxPermission($mailbox_id, $perm)
    {
        // Experimental feature.
        // This option does not affect admin users.
        if ($perm == Mailbox::ACCESS_PERM_ASSIGNED) {
            if ($this->isAdmin()) {
                return false;
            } else {
                $show_only_assigned_conversations = config('app.show_only_assigned_conversations') ?? '';
                if (in_array($this->id, explode(',', $show_only_assigned_conversations))) {
                    return true;
                } else {
                    return false;
                }
            }
        }

        if ($this->isAdmin()) {
            return true;
        } else {
            //$mailbox = $this->mailboxesCanViewWithSettings(true)->where('id', $mailbox_id)->first();
            $mailbox = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first();
            if ($mailbox && !empty($mailbox->access) && in_array($perm, json_decode($mailbox->access))) {
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * Generate random password for the user.
     *
     * @param int $length
     *
     * @return string
     */
    public static function generateRandomPassword($length = 8)
    {
        return str_random($length);
    }

    /**
     * This password indicates that the user has not set the password by himself.
     */
    public static function getDummyPassword()
    {
        return encrypt('dummy_'.str_random(8));
    }

    public function isDummyPassword()
    {
        $decrypted_password = \Helper::decrypt($this->password);
        return preg_match("#^dummy_#", $decrypted_password);
        //return Hash::check($this->getDummyPassword(), $this->password);
    }

    /**
     * Get URL for editing user.
     *
     * @return string
     */
    public function url()
    {
        return route('users.profile', ['id' => $this->id]);
    }

    /**
     * Get URL for settings up an account from invitation.
     *
     * @return string
     */
    public function urlSetup()
    {
        $invite_sent_at = \Helper::encrypt(time().'', $this->password);

        return route('user_setup', ['hash' => $this->invite_hash, 'invite_sent_at' => $invite_sent_at]);
    }

    /**
     * Create personal folders for user mailboxes.
     *
     * @param int   $mailbox_id
     * @param mixed $users
     */
    public function syncPersonalFolders($mailboxes)
    {
        if ($this->isAdmin()) {
            // For admin we get all mailboxes
            $mailbox_ids = Mailbox::pluck('mailboxes.id');
        } else {
            if (is_array($mailboxes)) {
                $mailbox_ids = $mailboxes;
            } else {
                $mailbox_ids = $this->mailboxes()->pluck('mailboxes.id');
            }
        }

        $cur_mailboxes = Folder::select('mailbox_id')
            ->where('user_id', $this->id)
            ->whereIn('mailbox_id', $mailbox_ids)
            ->groupBy('mailbox_id')
            ->pluck('mailbox_id')
            ->toArray();

        foreach ($mailbox_ids as $mailbox_id) {
            if (in_array($mailbox_id, $cur_mailboxes)) {
                continue;
            }
            foreach (Folder::$personal_types as $type) {
                Folder::create([
                    'mailbox_id' => $mailbox_id,
                    'user_id' => $this->id,
                    'type' => $type,
                ]);
            }
        }
    }

    /**
     * Format date according to user's timezone and time format.
     *
     * @param Carbon $date
     * @param string $format
     *
     * @return string
     */
    public static function dateFormat($date, $format = 'M j, Y H:i', $user = null, $modify_format = true, $use_user_timezone = true)
    {
        if (!$user) {
            $user = auth()->user();
        }
        if (is_string($date)) {
            // Convert string in to Carbon
            try {
                $date = Carbon::parse($date);
            } catch (\Exception $e) {
                $date = null;
            }
        }

        if (!$date) {
            return '';
        }

        if (!$format) {
            $format = 'M j, Y H:i';
        }

        if ($user && $user !== false) {
            if ($modify_format) {
                if ($user->time_format == self::TIME_FORMAT_12) {
                    $format = strtr($format, [
                        'H'     => 'h',
                        'G'     => 'g',
                        ':i'    => ':ia',
                        ':i:s'  => ':i:sa',
                        ':ia:s' => ':i:sa',
                    ]);
                } else {
                    $format = strtr($format, [
                        'h'     => 'H',
                        'g'     => 'G',
                        ':ia'   => ':i',
                        ':i:sa' => ':i:s',
                    ]);
                }
            }
            // todo: formatLocalized has to be used here and below,
            // but it returns $format value instead of formatted date
            if ($use_user_timezone) {
                $date->setTimezone($user->timezone);
            }
        }

        if (class_exists('IntlDateFormatter')) {

            // Convert `strftime` format to `IntlDateFormatter` pattern.
            // https://unicode-org.github.io/icu/userguide/format_parse/datetime/
            $format = strtr($format, [
                'M' => 'MMM',
                'm' => 'MM',
                'j' => 'd',
                'd' => 'dd',
                'H' => 'HH',
                'h' => 'hh',
                'i' => 'mm',
                's' => 'ss',
                'l' => 'cccc',
                'O' => 'xx',
                // https://stackoverflow.com/questions/59682843/php-intldateformatter-returns-incorrect-year
                'Y' => 'y',
            ]);

            // Remove dot from month name.
            $formatted = $date->formatLocalized($format);
            if (!strstr($format, '.')) {
                $formatted = str_replace('.', '', $formatted);
            }
            
            // AM/PM to am/pm.
            $formatted = preg_replace_callback('#\d+(AM|PM)$#', function ($m) {
                return strtolower($m[0] ?? '');
            }, $formatted);

            return \Helper::mbUcfirst($formatted);
        } else {
            return $date->format($format);
        }
    }

    /**
     * Convert date into human readable format.
     *
     * @param Carbon $date
     *
     * @return string
     */
    public static function dateDiffForHumans($date)
    {
        if (!$date) {
            return '';
        }

        if (is_string($date)) {
            // Convert string in to Carbon
            $date = Carbon::parse($date);
        }

        $user = auth()->user();
        if ($user) {
            $date->setTimezone($user->timezone);
        }

        if ($date->diffInSeconds(Carbon::now()) <= 60) {
            return __('Just now');
        } elseif ($date->diffInDays(Carbon::now()) > 7) {
            // Exact date
            if (Carbon::now()->year == $date->year) {
                return self::dateFormat($date, 'M j');
            } else {
                return self::dateFormat($date, 'M j, Y');
            }
        } else {
            $diff_text = $date->diffForHumans();
            $diff_text = preg_replace('/minute[sn]?/', 'min', $diff_text);

            return $diff_text;
        }
    }

    /**
     * Convert date into human readable format with minutes and hours.
     *
     * @param Carbon $date
     *
     * @return string
     */
    public static function dateDiffForHumansWithHours($date)
    {
        $dateForHuman = self::dateDiffForHumans($date);

        if (!$dateForHuman) {
            return '';
        }

        if (stripos($dateForHuman, 'just') === false) {
            return __(':date @ :time', ['date' => $dateForHuman, 'time' => self::dateFormat($date, 'H:i')]);
        } else {
            return $dateForHuman;
        }
    }

    public static function getUserPermissionName($user_permission)
    {
        $user_permission_names = [
            self::PERM_DELETE_CONVERSATIONS => __('Users are allowed to delete conversations'),
            self::PERM_EDIT_CONVERSATIONS   => __('Users are allowed to edit own notes/replies'),
            self::PERM_EDIT_SAVED_REPLIES   => __('Users are allowed to edit/delete saved replies'),
            self::PERM_EDIT_TAGS            => __('Users are allowed to manage tags'),
            self::PERM_EDIT_CUSTOM_FOLDERS  => __('Users are allowed to manage custom folders'),
            self::PERM_EDIT_USERS           => __('Users are allowed to manage users'),
        ];

        if (!empty($user_permission_names[$user_permission])) {
            return $user_permission_names[$user_permission];
        } else {
            return \Eventy::filter('user_permissions.name', '', $user_permission);
        }
    }

    public function getInviteStateName()
    {
        $names = [
            self::INVITE_STATE_ACTIVATED   => __('Active'),
            self::INVITE_STATE_SENT        => __('Invited'),
            self::INVITE_STATE_NOT_INVITED => __('Not Invited'),
        ];
        if (!isset($names[$this->invite_state])) {
            return $names[self::INVITE_STATE_ACTIVATED];
        } else {
            return $names[$this->invite_state];
        }
    }

    /**
     * Send invitation to this user.
     */
    public function sendInvite($throw_exceptions = false)
    {
        function saveToSendLog($user, $status)
        {
            SendLog::log(null, null, $user->email, SendLog::MAIL_TYPE_INVITE, $status, null, $user->id);
        }

        if ($this->invite_state == self::INVITE_STATE_ACTIVATED) {
            return false;
        }
        // We are using remember_token as a hash for invite
        if (!$this->invite_hash) {
            $this->setInviteHash();
            $this->save();
        }

        try {
            \App\Misc\Mail::setSystemMailDriver();

            \Mail::to([['name' => $this->getFullName(), 'email' => $this->email]])
                ->send(new UserInvite($this));
        } catch (\Exception $e) {
            // We come here in case SMTP server unavailable for example
            // But Mail does not through an exception if you specify incorrect SMTP details for example
            activity()
                ->causedBy($this)
                ->withProperties([
                    'error'    => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
                 ])
                ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
                ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_INVITE);

            saveToSendLog($this, SendLog::STATUS_SEND_ERROR);

            if ($throw_exceptions) {
                throw $e;
            } else {
                return false;
            }
        }

        if (\Mail::failures()) {
            saveToSendLog($this, SendLog::STATUS_SEND_ERROR);

            if ($throw_exceptions) {
                throw new \Exception(__('Error occurred sending email to :email. Please check logs for more details.', ['email' => $this->email]), 1);
            } else {
                return false;
            }
        }

        if ($this->invite_state != self::INVITE_STATE_SENT) {
            $this->invite_state = self::INVITE_STATE_SENT;
            $this->save();
        }

        saveToSendLog($this, SendLog::STATUS_ACCEPTED);

        return true;
    }

    /**
     * Generate and set password.
     */
    public function setPassword($password = null)
    {
        if ($password === null) {
            $password = $this->generateRandomPassword();
        }
        $this->password = Hash::make($password);
    }

    /**
     * Generate and set invite_hash.
     */
    public function setInviteHash()
    {
        $this->invite_hash = Str::random(60);
    }

    /**
     * Send password changed noitfication.
     */
    public function sendPasswordChanged()
    {
        function saveToSendLog($user, $status)
        {
            SendLog::log(null, null, $user->email, SendLog::MAIL_TYPE_PASSWORD_CHANGED, $status, null, $user->id);
        }

        try {
            \App\Misc\Mail::setSystemMailDriver();

            \Mail::to([['name' => $this->getFullName(), 'email' => $this->email]])
                ->send(new PasswordChanged($this));
        } catch (\Exception $e) {
            // We come here in case SMTP server unavailable for example
            // But Mail does not through an exception if you specify incorrect SMTP details for example
            activity()
                ->causedBy($this)
                ->withProperties([
                    'error'    => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
                 ])
                ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
                ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_PASSWORD_CHANGED);

            saveToSendLog($this, SendLog::STATUS_SEND_ERROR);

            return false;
        }

        if (\Mail::failures()) {
            saveToSendLog($this, SendLog::STATUS_SEND_ERROR);

            return false;
        }

        saveToSendLog($this, SendLog::STATUS_ACCEPTED);

        return true;
    }

    /**
     * Send the password reset notification.
     *
     * @param string $token
     *
     * @return void
     */
    public function sendPasswordResetNotification($token)
    {
        \App\Misc\Mail::setSystemMailDriver();

        $this->notify(new ResetPasswordNotification($token));
    }

    public function getWebsiteNotifications()
    {
        return $this->notifications()->paginate(self::WEBSITE_NOTIFICATIONS_PAGE_SIZE, ['*'], self::WEBSITE_NOTIFICATIONS_PAGE_PARAM, request()->wn_page);
    }

    public function getWebsiteNotificationsInfo($cache = true)
    {
        if ($cache) {
            // Get from cache
            $user = $this;

            return \Cache::rememberForever('user_web_notifications_'.$user->id, function () use ($user) {
                $notifications = $user->getWebsiteNotifications();

                $info = [
                    'data'          => WebsiteNotification::fetchNotificationsData($notifications),
                    'notifications' => $notifications,
                    'unread_count'  => $user->unreadNotifications()->count(),
                ];

                $info['html'] = view('users/partials/web_notifications', ['web_notifications_info_data' => $info['data']])->render();

                return $info;
            });
        } else {
            $notifications = $this->getWebsiteNotifications();

            $info = [
                'data'          => WebsiteNotification::fetchNotificationsData($notifications),
                'notifications' => $notifications,
                'unread_count'  => $this->unreadNotifications()->count(),
            ];

            return $info;
        }
    }

    public function clearWebsiteNotificationsCache()
    {
        \Cache::forget('user_web_notifications_'.$this->id);
    }

    public function getPhotoUrl($default_if_empty = true)
    {
        if (!empty($this->photo_url) || !$default_if_empty) {
            if (!empty($this->photo_url)) {
                return Storage::url(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url);
            } else {
                return '';
            }
        } else {
            return asset('/img/default-avatar.png');
        }
    }

    /**
     * Resize and save user photo.
     *
     * $uploaded_file can be \File or string.
     */
    public function savePhoto($uploaded_file, $mime_type = '')
    {
        $real_path = $uploaded_file;
        if (!is_string($uploaded_file)) {
            // Fallback to getPathname() for Windows.
            // https://github.com/freescout-help-desk/freescout/issues/4105
            $real_path = $uploaded_file->getRealPath() ?: $uploaded_file->getPathname();
            $mime_type = $uploaded_file->getMimeType();
        }

        $photo_size = config('app.user_photo_size');
        $resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, $photo_size, $photo_size);

        if (!$resized_image) {
            return false;
        }

        $file_name = md5(Hash::make($this->id)).'.jpg';
        $dest_path = Storage::path(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$file_name);

        $dest_dir = pathinfo($dest_path, PATHINFO_DIRNAME);
        if (!file_exists($dest_dir)) {
            \File::makeDirectory($dest_dir, 0755);
        }

        // Remove current photo
        if ($this->photo_url) {
            Storage::delete(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url);
        }

        imagejpeg($resized_image, $dest_path, self::PHOTO_QUALITY);
        // $photo_url = $request->file('photo_url')->storeAs(
        //     User::PHOTO_DIRECTORY, !Hash::make($user->id).'.jpg'
        // );

        return $file_name;
    }

    /**
     * Remove user photo.
     */
    public function removePhoto()
    {
        if ($this->photo_url) {
            Storage::delete(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url);
        }
        $this->photo_url = '';
    }

    public function hasPermission($permission, $check_own_permissions = true)
    {
        $has_permission = false;

        $global_permissions = self::getGlobalUserPermissions();

        if (!empty($global_permissions) && is_array($global_permissions) && in_array($permission, $global_permissions)) {
            $has_permission = true;
        }

        if ($check_own_permissions && !empty($this->permissions)) {
            if (isset($this->permissions[$permission])) {
                $has_permission = (bool)$this->permissions[$permission];
            }
        }

        return $has_permission;
    }

    public static function getGlobalUserPermissions()
    {
        $permissions = [];
        $permissions_json = config('app.user_permissions');

        if ($permissions_json) {
            $permissions_json = base64_decode($permissions_json);
            try {
                $permissions = json_decode($permissions_json, true);
            } catch (\Exception $e) {
                // Do nothing.
            }
        }

        if (!is_array($permissions)) {
            $permissions = [];
        }

        return $permissions;
    }

    /**
     * Todo: implement super admin role.
     * For now we return just first admin.
     *
     * @return [type] [description]
     */
    public static function getSuperAdmin()
    {
        return self::nonDeleted()->where('role', self::ROLE_ADMIN)->first();
    }

    /**
     * Create user.
     */
    public static function create($data, $options = [])
    {
        $user = new self();

        if (empty($data['email']) || empty($data['password'])) {
            return null;
        }

        $user->setData($data);

        try {
            $user->save();
        } catch (\Exception $e) {
            \Helper::logException($e);
            return null;
        }

        return $user;
    }

    /**
     * Set fields.
     */
    public function setData($data, $replace_data = true, $save = false)
    {
        if (isset($data['email'])) {
            $data['email'] = Email::sanitizeEmail($data['email']);
        }
        if (isset($data['password']) && empty($data['no_password_hashing'])) {
            $data['password'] = \Hash::make($data['password']);
        }

        // Strip tags.
        $fields_strip = [
            'first_name',
            'last_name',
            'phone',
            'timezone',
        ];

        foreach ($fields_strip as $field) {
            if (in_array($field, array_keys($data))) {
                if ($data[$field] !== null) {
                    $data[$field] = \Helper::stripTags($data[$field]);
                }
            }
        }

        if ($replace_data) {
            $this->fill($data);
        } else {
            // Update empty fields.
            foreach ($data as $key => $value) {
                if (in_array($key, $this->fillable) && empty($this->$key)) {
                    $this->$key = $value;
                }
            }
        }
        // Role is not fillable.
        if (!empty($data['role']) && array_key_exists((int)$data['role'], self::$roles)) {
            $this->role = (int)$data['role'];
        }

        \Eventy::action('user.set_data', $this, $data, $replace_data);

        if ($save) {
            $this->save();
        }
    }

    /**
     * Check if current user's role is higher than passed.
     */
    public static function checkRole($role)
    {
        $user = auth()->user();
        if ($user) {
            return $user->role >= $role;
        } else {
            return false;
        }
    }

    /**
     * Get dummy user, for example, when real user has been deleted.
     */
    public static function getDeletedUser()
    {
        $user = new self();
        $user->first_name = 'DELETED';
        $user->last_name = 'DELETED';
        $user->email = 'deleted@example.org';

        return $user;
    }

    /**
     * Get user locale.
     *
     * @return [type] [description]
     */
    public function getLocale()
    {
        if ($this->locale) {
            return $this->locale;
        } else {
            return \Helper::getRealAppLocale();
        }
    }

    /**
     * Get query to fetch non-deleted users.
     * Some modules may extend this condition, to allow this user $extended parameter.
     *
     * @return [type] [description]
     */
    public static function nonDeleted($extended = false)
    {
        $condition = self::where('status', '!=', self::STATUS_DELETED);

        return \Eventy::filter('user.non_deleted_condition', $condition, $extended);
    }

    public function isActive()
    {
        return $this->status == self::STATUS_ACTIVE;
    }

    public function isDisabled()
    {
        return $this->status == self::STATUS_DISABLED;
    }

    public function isDeleted()
    {
        return $this->status == self::STATUS_DELETED;
    }

    /**
     * Get users which current user can see.
     */
    public function whichUsersCanView($mailboxes = null, $sort = true)
    {
        if ($this->isAdmin()) {
            $users = User::nonDeleted()->get();
        } else {
            // Get user mailboxes.
            if ($mailboxes == null) {
                $mailbox_ids = $this->mailboxesIdsCanView();
            } else {
                $mailbox_ids = $mailboxes->pluck('id')->toArray();
            }

            // Get users
            $users = User::nonDeleted()->select('users.*')
                ->join('mailbox_user', function ($join) {
                    $join->on('mailbox_user.user_id', '=', 'users.id');
                })
                ->whereIn('mailbox_user.mailbox_id', $mailbox_ids)
                ->groupBy('users.id')
                ->get();
        }

        if ($sort) {
            $users = User::sortUsers($users);
        }

        return $users;
    }

    /**
     * Get user initials: FL.
     */
    public function getInitials($length = 2)
    {
        if ($length == 2) {
            return strtoupper(mb_substr($this->first_name, 0, 1)).strtoupper(mb_substr($this->last_name, 0, 1));
        } else {
            return strtoupper(mb_substr($this->first_name, 0, 1));
        }
    }

    public function getAuthToken()
    {
        $expiry = time()+2592000;
        $hash = hash_hmac('sha256', $this->id.':'.$expiry, config('app.key').$this->password);

        return urlencode(base64_encode($this->id.':'.$expiry.':'.$hash));
    }

    public static function findNonDeleted($id, $extended = false)
    {
        return User::nonDeleted($extended)->where('id', $id)->first();
    }

    /**
     * Sorting users alphabetically.
     * It has to be done in PHP.
     */
    public static function sortUsers($users)
    {
        $users = $users->sortBy(function ($user, $i) {
            return $user->getFullName();
        }, SORT_STRING | SORT_FLAG_CASE);

        return $users;
    }

    public static function getUserPermissionsList()
    {
        return \Eventy::filter('user_permissions.list', self::$user_permissions);
    }

    /**
     * Check user main and alternate emails.
     */
    public function hasEmail($email)
    {
        $email = Email::sanitizeEmail($email);

        if ($this->email == $email) {
            return true;
        }
        $alt_emails = explode(',', $this->emails ?? '');
        
        foreach ($alt_emails as $alt_email) {
            if (Email::sanitizeEmail($alt_email) == $email) {
                return true;
            }
        }

        return false;
    }

    public static function findByAlternateEmail($email)
    {
        $email = Email::sanitizeEmail($email);

        $users = self::nonDeleted()
            ->where('emails', \Helper::sqlLikeOperator(), '%'.$email.'%')
            ->get();

        foreach ($users as $user) {
            if ($user->hasEmail($email)) {
                return $user;
            }
        }

        return null;
    }

    /**
     * Check if there is a mailbox with specified email.
     */
    public static function mailboxEmailExists($email)
    {
        $email = Email::sanitizeEmail($email);
        $mailbox = Mailbox::where('email', $email)->first();

        if ($mailbox) {
            return true;
        } else {
            return false;
        }
    }

    public function followConversation($conversation_id)
    {
        try {
            $follower = new Follower();
            $follower->conversation_id = $conversation_id;
            $follower->user_id = $this->id;
            $follower->save();
        } catch (\Exception $e) {
            // Already exists
        }
    }

    // If there will be some issues, extra "robot" field
    // may need to be added to Users table.
    public static function getRobotsCondition()
    {
        return User::where('type', User::TYPE_ROBOT);
    }

    // Truncate fields to their max lengths to avoid PostgreSQL error:
    // SQLSTATE[22001]: String data, right truncated: 7 ERROR: value too long for type character varying(100).
    // https://github.com/freescout-helpdesk/freescout/issues/3489
    public function setFirstNameAttribute($first_name)
    {
        $this->attributes['first_name'] = mb_substr($first_name ?? '', 0, 20);
    }
    public function setLastNameAttribute($last_name)
    {
        $this->attributes['last_name'] = mb_substr($last_name ?? '', 0, 30);
    }
    public function setEmailAttribute($email)
    {
        $this->attributes['email'] = mb_substr($email ?? '', 0, 100);
    }
    public function setJobTitleAttribute($job_title)
    {
        $this->attributes['job_title'] = mb_substr($job_title ?? '', 0, 100);
    }

    public function canSeeOnlyAssignedConversations()
    {
        return $this->hasManageMailboxPermission(0, Mailbox::ACCESS_PERM_ASSIGNED);
    }

    public function deleteUser($auth_user, $assign_user)
    {
        // We have to process conversations one by one to move them to Unassigned folder,
        // as conversations may be in different mailboxes
        // $this->conversations()->update(['user_id' => null, 'folder_id' => ]);
        $mailbox_unassigned_folders = [];

        $this->conversations->each(function ($conversation) use ($auth_user, $assign_user) {
            // We don't fire ConversationUserChanged event to avoid sending notifications to users
            if (!empty($assign_user) 
                && !empty($assign_user[$conversation->mailbox_id]) 
                && (int) $assign_user[$conversation->mailbox_id] != -1
            ) {
                // Set assignee.
                // In this case conversation stays assigned, just assignee changes.
                $conversation->user_id = $assign_user[$conversation->mailbox_id];

            } else {

                // Make convesation Unassigned.
                
                // Unset assignee.
                // Maybe use changeUser() here.
                $conversation->user_id = null;

                if ($conversation->isPublished()
                    && ($conversation->isActive() || $conversation->isPending())
                ) {
                    // Change conversation folder to UNASSIGNED.
                    $folder_id = null;
                    if (!empty($mailbox_unassigned_folders[$conversation->mailbox_id])) {
                        $folder_id = $mailbox_unassigned_folders[$conversation->mailbox_id];
                    } else {
                        $folder = $conversation->mailbox->folders()
                            ->where('type', Folder::TYPE_UNASSIGNED)
                            ->first();

                        if ($folder) {
                            $folder_id = $folder->id;
                            $mailbox_unassigned_folders[$conversation->mailbox_id] = $folder_id;
                        }
                    }
                    if ($folder_id) {
                        $conversation->folder_id = $folder_id;
                    }
                }
            }

            $conversation->save();

            // Create lineitem thread
            $thread = new Thread();
            $thread->conversation_id = $conversation->id;
            $thread->user_id = $conversation->user_id;
            $thread->type = Thread::TYPE_LINEITEM;
            $thread->state = Thread::STATE_PUBLISHED;
            $thread->status = Thread::STATUS_NOCHANGE;
            $thread->action_type = Thread::ACTION_TYPE_USER_CHANGED;
            $thread->source_via = Thread::PERSON_USER;
            $thread->source_type = Thread::SOURCE_TYPE_WEB;
            $thread->customer_id = $conversation->customer_id;
            $thread->created_by_user_id = $auth_user->id;
            $thread->save();
        });

        // Recalculate counters for folders
        //if ($this->isAdmin()) {
        // Admin has access to all mailboxes
        Mailbox::all()->each(function ($mailbox) {
            $mailbox->updateFoldersCounters();
        });
        // } else {
        //     $this->mailboxes->each(function ($mailbox) {
        //         $mailbox->updateFoldersCounters();
        //     });
        // }

        // Disconnect user from mailboxes.
        $this->mailboxes()->sync([]);
        $this->folders()->delete();

        $this->status = \App\User::STATUS_DELETED;
        // Update email.
        $email_suffix = User::EMAIL_DELETED_SUFFIX.date('YmdHis');
        // We have to truncate email to avoid "Data too long" error.
        $this->email = mb_substr($this->email, 0, User::EMAIL_MAX_LENGTH - mb_strlen($email_suffix)).$email_suffix;

        $this->save();

        event(new UserDeleted($this, $auth_user));
    }
}
