<?php

namespace App\Models;

use Laravel\Sanctum\HasApiTokens;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Carbon\Carbon;

use App\Models\Role;
use App\Models\FileUpload;
use App\Models\Permission;
use App\Models\LivestreamRegistration;
use App\Models\StaffProfile;

use App\Mail\EmailVerification;

use Laravel\Socialite\Two\User as SocialiteUser;
use Google_Client;
use Google_Service_Directory;

use App\Traits\SearchTrait;
use App\Traits\UsesPermissionsTrait;

use App\Events\EditingToggled;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use Notifiable;
    use SearchTrait;
    use UsesPermissionsTrait;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

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

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'editing' => 'boolean',
        'dark_mode' => 'boolean',
        'activated_at' => 'datetime',
        'email_verified_at' => 'datetime',
        'banned_at' => 'datetime',
    ];

    protected $appends = ['search_label', 'class_name'];

    public function getSearchLabelAttribute()
    {
        return $this->name;
    }

    public function getSearchFieldsAttribute()
    {
        return [
            'name',
        ];
    }

    /**
     * After succesful login by the OAuth and regular functions in this controller
     * set the session variables
     */
    public function setSessionTimeout()
    {
        session()->put('timeout', now()->addMinutes((int) config('session.lifetime')));
    }

    public function saveUser(array $input, $id = null)
    {
        if ($id) {
            $user = User::findOrFail($id);
        } else {
            $user = new User();
        }

        $user->name = Arr::get($input, 'name');
        $user->email = strtolower(Arr::get($input, 'email'));

        if (!$id) {
            $user->password = Hash::make(Arr::get($input, 'password') ?? Str::random(40));
            if (Arr::get($input, 'password')) {
                $user->activated_at = now();
            }
        } else {
            if (Arr::get($input, 'password') && auth()->check()) {
                if (auth()->user()->can('update', $user)) {
                    $user->password = Hash::make(Arr::get($input, 'password'));
                }
            }
        }

        $user->save();

        if (!$id && Arr::get($input, 'password')) {
            Mail::to($user->email)
                ->queue(new EmailVerification($user));
        }

        if (auth()->user()?->hasRole('admin')) {
            $user->roles()->detach();
            if (Arr::get($input, 'roles')) {
                foreach (Arr::get($input, 'roles') as $role_data) {
                    $user->addRole(Role::findOrFail(Arr::get($role_data, 'id')));
                }
            }
        }

        cache()->tags([cache_name($user)])->flush();

        return $user;
    }

    public static function saveOrCreate($input)
    {
        $id = null;
        $user = User::where('email', Arr::get($input, 'email'))->first();

        if ($user) {
            $id = $user->id;
        }

        return (new User())->saveUser($input, $id);
    }

    public static function findOrCreateByEmail($input)
    {
        $id = null;
        $user = User::where('email', Arr::get($input, 'email'))->first();

        if ($user) {
            return $user;
        } else {
            return (new User())->saveUser($input, $id);
        }
    }

    public function inquiries()
    {
        return $this->hasMany(Inquiry::class);
    }

    public function livestreamRegistrations()
    {
        return $this->hasMany(LivestreamRegistration::class);
    }

    public function livestreams()
    {
        return $this->hasManyThrough(Livestream::class, LivestreamRegistration::class, 'user_id', 'id', 'id', 'livestream_id');
    }

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    public function addRole($role)
    {
        if (!$role instanceof Role) {
            $role_name = $role;
            $role = Role::where('name', $role)->first();
        }

        if (!$role instanceof Role) {
            throw new ModelNotFoundException('There is no role with the name '.$role_name);
        }

        if (!$this->roles->contains('id', $role->id)) {
            $this->roles()->attach($role);
        }

        cache()->tags([cache_name($this)])->flush();
        return $this;
    }

    public function removeRole($role)
    {
        if (!$role instanceof Role) {
            $role_name = $role;
            $role = Role::where('name', $role)->first();
        }

        if (!$role instanceof Role) {
            throw new ModelNotFoundException('There is no role with the name '.$role_name);
        }

        $this->roles()->detach($role);

        cache()->tags([cache_name($this)])->flush();
        return $this;
    }

    public function hasRole($role)
    {
        return cache()->tags([cache_name($this), cache_name($role)])->rememberForever(cache_name($this).'-has-role-'.cache_name($role), function () use ($role) {
            if (is_int($role)) {
                $role = Role::findOrFail($role);
            }

            if (!$role instanceof Role) {
                $role_name = $role;
                $role = Role::where('name', $role)->first();
            }

            if (!$role instanceof Role) {
                throw new ModelNotFoundException('There is no role with the name '.$role_name);
            }

            if ($this->roles->contains('name', 'admin')) {
                return true;
            }

            return $this->roles->contains('id', $role->id);
        });
    }

    public static function createOrUpdateFromGoogle(SocialiteUser $data)
    {
        $validator = Validator::make([
            'id' => $data->getId(),
            'name' => $data->getName(),
            'email' => $data->getEmail(),
            'avatar' => $data->getAvatar(),
        ], [
            'id' => 'required',
            'email' => 'required|email',
            'name' => 'required:max:255',
        ])->validate();

        $user = User::where('email', $data->getEmail())->first();

        if (!$user instanceof User) {
            $user = new User();
            $user->password = Str::random(40);
        }

        $user->oauth_id = $data->getId();
        $user->email = $data->getEmail();
        $user->name = $data->getName();
        $user->avatar = $data->getAvatar();
        $user->email_verified_at = now();
        $user->save();

        return $user;
    }

    public function setGroupsFromGoogle()
    {
        $client = new Google_Client();
        $client->setScopes(Google_Service_Directory::ADMIN_DIRECTORY_GROUP_READONLY);
        $client->setAuthConfig(base_path('sa/directory.json'));
        $client->setSubject('sa_developer@brentwood.ca');

        $service = new Google_Service_Directory($client);
        $groups = collect($service->groups->listGroups(['domain' => 'brentwood.ca', 'userKey' => $this->email])->groups)->pluck('name', 'id')
                        ->filter(function ($role_name, $role_id) {
                            return Str::startsWith(Str::lower($role_name), Role::googlePrefixes());
                        });

        $this->removeGoogleGroups();

        foreach ($groups as $id => $name) {
            $role = Role::where('name', $name)->first();

            if (!$role instanceof Role) {
                $role = new Role();
                $role->name = $name;
                $role->oauth_id = $id;
                $role->save();
            }
            $this->addRole($role);
        }

        return $this;
    }

    private function removeGoogleGroups()
    {
        foreach ($this->roles as $role) {
            if (Str::startsWith(Str::lower($role->name), Role::googlePrefixes())) {
                $this->removeRole($role);
            }
        }
        $this->refresh();
        return $this;
    }

    public function chats()
    {
        return $this->hasMany(Chat::class);
    }

    public function whispers()
    {
        return $this->belongsToMany(Chat::class, 'whispers');
    }

    public function getEmailVerificationUrl()
    {
        return URL::temporarySignedRoute('users.verify-email', Carbon::now()->addMinutes((int) Config::get('auth.verification.expire', 60)), [ 'id' => $this->id ]);
    }

    public function getResetPasswordUrl()
    {
        return URL::temporarySignedRoute('users.reset-password', Carbon::now()->addMinutes((int) Config::get('auth.verification.expire', 60)), [ 'id' => $this->id ]);
    }

    public function canPerformAction($action, $objectable)
    {
        $check = cache()->tags([cache_name($objectable), cache_name($this)])->rememberForever(cache_name($this).'-can-perform-'.cache_name($objectable).'-'.$action, function () use ($action, $objectable) {
            if ($this->permissions
                    ->where('action', $action)
                    ->where('objectable_id', $objectable->id)
                    ->where('objectable_type', get_class($objectable))
                    ->count() > 0) {
                return true;
            }

            foreach ($this->roles as $role) {
                if ($role->canPerformAction($action, $objectable)) {
                    return true;
                }
            }

            if ($objectable instanceof Page) {
                $version = $objectable->version;
                if ($version) {
                    $parent_page_id = $version->parent_page_id;
                    if ($parent_page_id  > 1) {
                        $parent_page = Page::find($parent_page_id);
                        if ($this->canPerformAction($action, $parent_page)) {
                            return true;
                        }
                    }
                }
            }

            return false;
        });
        return $check;
    }

    public function registerForLivestream(Livestream $livestream)
    {
        $livestream->registerUser($this);
    }

    public function staffProfile()
    {
        return $this->hasOne(StaffProfile::class);
    }

    public function createStaffProfile()
    {
        $input = [
            'first_name' => $this->first_name,
            'last_name' => $this->last_name,
        ];
        $staff_profile = (new StaffProfile())->saveStaffProfile($input);
        $staff_profile->user_id = $this->id;
        $staff_profile->save();
        return $staff_profile;
    }

    public function enableEditing($enable = true)
    {
        $this->editing = $enable;
        $this->save();
        $this->refresh();
        broadcast(new EditingToggled($this));
        return $this;
    }

    public function disableEditing()
    {
        $this->enableEditing(false);
    }

    public function getFirstNameAttribute()
    {
        $array = explode(' ', $this->name, 2);
        return isset($array[0]) ? $array[0] : '';
    }

    public function getLastNameAttribute()
    {
        $array = explode(' ', $this->name, 2);
        return isset($array[1]) ? $array[1] : '';
    }

    public function isGuardian() 
    {
        try {
            $result = DB::connection('myschool')
                ->select("
                    SELECT contacts.user_id,
                         contacts.surname,
                         contacts.name,
                         contacts.user_email
                    FROM
                        contacts
                    LEFT JOIN
                        relationships ON contacts.user_id = relationships.contact_user_id
                    WHERE
                     contacts.user_email = '".$this->email."'
                     AND
                     relationships.is_main > 0
                     AND
                     relationships.student_user_id IN
                     (SELECT
                          con.user_id
                     FROM students AS s
                     LEFT JOIN
                          contacts AS con ON con.user_id = s.user_id
                     LEFT JOIN
                          class_students AS cs ON cs.user_id = s.user_id AND cs.user_status_id = 1
                     LEFT JOIN
                          classes AS cls ON cls.class_id = cs.class_id AND cls.year = IF(MONTH(CURDATE()) >= 8, YEAR(CURDATE())+1, YEAR(CURDATE()))
                     LEFT JOIN
                          class_levels AS clslvl ON clslvl.class_level_id = cls.class_level_id
                     WHERE
                          clslvl.class_level_label IN ('Grade 8', 'Grade 9', 'Grade 10', 'Grade 11', 'Grade 12')
                     )"
                );

            return count($result) > 0 ? true : false;
        } catch (\Exception $e) {
            return false;
        }
    }
}
