<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Models\GroupPermission;
use App\Models\ProductRights;
use Illuminate\Database\Eloquent\SoftDeletes;

use App\Casts\MfaEncrypted;
use App\Jobs\SendAuthEmail;
use App\Traits\Owned;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable;
    use SoftDeletes;
    use Owned;
    use HasApiTokens;

    protected $fillable = [
        'name',
        'email',
        'phone',
        'password',
        'question_one',
        'question_two',
        'question_three',
        'answer_one',
        'answer_two',
        'answer_three',
        'billing',
        'signup_token',
        'reset_token',
        'group_id',
        'organisation_id',
        'suspended_at',
        'reset_at',
        'signup_at',
        'login_at',
        'refreshed_at'
    ];

    protected $hidden = [
        'password',
        'answer_one',
        'answer_two',
        'answer_three',
        'signup_token',
        'reset_token',
        'remember_token',
        'totp_secret',
    ];

    protected $casts = [
        'reset_at' => 'datetime',
        'signup_at' => 'datetime',
        'login_at' => 'datetime',
        'totp_secret' => MfaEncrypted::class,
        'totp_confirmed_at' => 'datetime',
    ];

    public static function generateEmailToken($_field) {
        $length = 32;
        $email_token = Str::random($length);
        
        while(self::where($_field, '=', $email_token)->count()) {
            $email_token = Str::random($length++);
        }

        return $email_token;
    }

    public function sendSetupEmail() {
        // Update user itself
        $this->signup_token = self::generateEmailToken('signup_token');
        $this->save();

        // Queue up email
        SendAuthEmail::dispatch('setup', $this->email, $this->signup_token);
    }

    public function sendResetEmail() {
        // Create our reset token
        $token = Str::random(32);

        while(CodeUserReset::withTrashed()->where('token', '=', hash('sha256', $token))->count()) {
            $token = Str::random(32);
        }

        CodeUserReset::create([
            'user_id'   => $this->id,
            'token'     => hash('sha256', $token),
            'expiry_at' => Carbon::now()->addHours(2)
        ]);

        // Queue up email
        SendAuthEmail::dispatch('reset', $this->email, $token);
    }

    // Only a confirmed enrollment enforces MFA - A set secret alone is still mid-setup.
    public function mfaEnabled() {
        return $this->totp_confirmed_at !== null;
    }

    // Gets the permissions for a given user minus any they fail a conditional check for.
    public function getPermissions() {
        if($this->group_id === null) {
            return [];
        }

        $filtered_permissions = [];
        $unfiltered_permissions = GroupPermission::where('group_id', '=', $this->group_id)->get()->toArray();

        $product_access = ProductRights::where('organisation_id', '=', $this->organisation_id)->get()->pluck('name')->toArray();

        $externals = OrganisationExternalIdentities::where('organisation_id', '=', $this->organisation_id)->select('key')->get()->keyBy('key');

        foreach($unfiltered_permissions as $x) {

            if(isset($x['identifier']) && $x['identifier'] && !isset($externals[$x['identifier']])) {
                continue;
            }

            if($x['status'] == 1 && ($x['conditional'] == null || array_search($x['conditional'], $product_access) !== false)) {
                $filtered_permissions[] = $x['permission'];
            }
        }

        return $filtered_permissions;
    }

    public function canDo($_x) {
        if($this->group_id === null) {
            return false;
        }

        if(!is_array($_x)) {
            $_x = (array)$_x;
        }

        $granted = $this->getPermissions();

        foreach($_x as $key) {
            if($key === null || $key === '' || !in_array($key, $granted, true)) {
                return false;
            }
        }

        return true;
    }
}
