<?php

namespace Ignite\Core\Entities;

use Exception;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Auth;
use Ignite\Core\Audit\ParticipantDriver;
use OwenIt\Auditing\Auditable;
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
use OwenIt\Auditing\Contracts\UserResolver;

/**
 * User
 *
 * @property int user_id
 * @property string username
 * @property string email
 * @property string first
 * @property string last
 * @property string password
 * @property string password_salt
 * @property int status
 * @property int internal
 * @property string remember_token
 * @property string last_login_at
 * @property string created_at
 * @property string updated_at
 * @property \Illuminate\Database\Eloquent\Collection groups
 */
class User extends Authenticatable implements AuditableContract
{
    use Auditable;
    use Notifiable;

    const STAT_INACTIVE = 0;
    const STAT_ACTIVE = 1;
    const STAT_PENDING = 2;
    const TYPE_INTERNAL = 1;
    const TYPE_PARTICIPANT = 0;

    /**
     * @var string
     */
    protected $table = 'core_user';

    /**
     * @var string
     */
    protected $primaryKey  = 'user_id';

    /**
     * Custom Audit Driver
     *
     * @var ParticipantDriver
     */
    protected $auditDriver = ParticipantDriver::class;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'first', 'last', 'username', 'email', 'password', 'status', 'internal',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'password_salt', 'remember_token',
    ];

    /**
     * @var array
     */
    protected $casts = [
        'last_login_at' => 'datetime'
    ];

    /**
     * Get s list of user types.
     *
     * @return array
     * @throws Exception
     */
    public static function getTypeList()
    {
        return [
            static::TYPE_PARTICIPANT => 'Participant',
            static::TYPE_INTERNAL => 'Internal',
        ];
    }

    /**
     * Get s list of user statuses.
     *
     * @return array
     * @throws Exception
     */
    public static function getStatusList()
    {
        return [
            static::STAT_INACTIVE => 'Inactive',
            static::STAT_PENDING => 'Pending',
            static::STAT_ACTIVE => 'Active',
        ];
    }

    /**
     * Determine whether the user is inactive.
     *
     * @return bool
     */
    public function isInactive()
    {
        return $this->status == static::STAT_INACTIVE;
    }

    /**
     * Determine whether the user is active.
     *
     * @return bool
     */
    public function isActive()
    {
        return $this->status == static::STAT_ACTIVE;
    }

    /**
     * Determine whether the user is pending.
     *
     * @return bool
     */
    public function isPending()
    {
        return $this->status == static::STAT_PENDING;
    }

    /**
     * User groups relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function groups()
    {
        return $this->belongsToMany(Group::class, 'core_user_group', 'user_id', 'group_id')->withTimestamps();
    }

    /**
     * Participant relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function participant()
    {
        return $this->hasOne(Participant::class, 'user_id', 'user_id');
    }

    /**
     * Notes relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function notes()
    {
        return $this->hasMany(Note::class, 'table_primary_id', 'user_id')
            ->whereIn('table_name', ['user', 'participant'])
            ->orderBy('created_at', 'desc');
    }

    /**
     * Get the currently authenticated user id or null.
     *
     * {@inheritdoc}
     */
    public static function resolveId()
    {
        return Auth::check() ? Auth::user()->getAuthIdentifier() : null;
    }

    /**
     * The user's full name.
     *
     * @return string
     */
    public function fullName()
    {
        return $this->first . ' ' . $this->last;
    }

    /**
     * The identifier for the record in the audit log.
     *
     * @return mixed
     */
    public static function getAuditFriendlyField()
    {
        return ['first', 'last'];
    }

    /**
     * Assign the user to the provided group.
     *
     * @param  Group|string $group
     * @return $this
     */
    public function assignToGroup($group)
    {
        if (is_string($group)) {
            $group = Group::whereName($group)->firstOrFail();
        }

        if (! $this->relationLoaded('groups')) {
            $this->load('groups');
        }

        if (! $this->groups->contains($group)) {
            $this->groups()->save($group);
        }

        return $this;
    }

    /**
     * Determine if a user is associated to a group.
     *
     * @param  string|array $group
     * @return bool
     */
    public function hasGroup($group)
    {
        if (is_int($group)) {
            return $this->groups->contains('id', $group);
        }

        if (is_string($group)) {
            return $this->groups->contains('key', $group);
        }

        if ($group instanceof Group) {
            return $this->groups->contains($group);
        }

        if (is_iterable($group)) {
            foreach ($group as $g) {
                if ($this->hasGroup($g)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Determine whether the user has the given permission.
     *
     * @param  Permission|string $permission
     * @return bool
     */
    public function hasPermission($permission)
    {
        /** @var Group $group */
        foreach ($this->groups as $group) {
            if ($group->hasPermission($permission)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get a flat list of permissions across all associated groups.
     *
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function getPermissions()
    {
        return $this->groups->flatMap(function ($group) {
            return $group->permissions->keyBy('key')->map(function ($permission) {
                return $permission;
            });
        });
    }

    /**
     * The lowest number level is the highest level privilege. i.e. Level 1 is a super admin.
     *
     * @return int
     * @throws Exception
     */
    public function getLevel()
    {
        $level = (int) $this->groups->min('level');

        if (empty($level)) {
            // TODO: Create a custom Exception class for this case.
            throw new \Exception('User does not have a configured level.');
        }

        return $level;
    }
}
