<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
use Laravel\Passport\HasApiTokens;

use Leenooks\Carbon;
use Leenooks\Traits\UserSwitch;
use App\Notifications\ResetPassword as ResetPasswordNotification;
use App\Models\Site;
use App\Models\Service;
use Spinen\QuickBooks\HasQuickBooksToken;

class User extends Authenticatable
{
	use HasApiTokens,Notifiable,UserSwitch,HasQuickBooksToken;

	protected $appends = [
		'active_display',
		'services_count_html',
		'surfirstname',
		'switch_url',
		'user_id_url',
	];

	protected $dates = [
		'created_at',
		'updated_at',
		'last_access'
	];

	/**
	 * 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',
	];

	protected $visible = [
		'active_display',
		'id',
		'level',
		'services_count_html',
		'switch_url',
		'surfirstname',
		'user_id_url',
	];

	protected $with = ['accounts'];

	/**
	 * Role hierarchy order
	 * @var array
	 */
	public static $role_order = [
		'wholesaler',
		'reseller',
		'customer',
	];

	/**
	 * The accounts that this user manages
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasMany
	 */
	public function accounts()
	{
		return $this->hasMany(Models\Account::class);
	}

	/**
	 * The agents that this users manages
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasMany
	 */
	public function agents() {
		return $this->hasMany(static::class,'parent_id','id')->with('agents');
	}

	/**
	 * The clients that this user has
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasMany
	 */
	public function clients() {
		return $this
			->hasMany(static::class,'parent_id','id')
			->with('clients');
	}

	/**
	 * This users language configuration
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
	 */
	public function language()
	{
		return $this->belongsTo(Models\Language::class);
	}

	/**
	 * This users invoices
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
	 */
	public function invoices()
	{
		return $this->hasManyThrough(Models\Invoice::class,Models\Account::class);
	}

	/**
	 * The payments this user has made
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
	 */
	public function payments()
	{
		return $this->hasManyThrough(Models\Payment::class,Models\Account::class);
	}

	/**
	 * THe services this user has
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
	 */
	public function services()
	{
		return $this->hasManyThrough(Models\Service::class,Models\Account::class);
	}

	/**
	 * The site this user is configured to access
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
	 */
	public function site()
	{
		return $this->belongsTo(Site::class);
	}

	/**
	 * This users supplier/reseller
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
	 */
	protected function supplier()
	{
		return $this->belongsTo(static::class,'parent_id','id');
	}

	/**
	 * Who this user supplies to
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\HasMany
	 */
	protected function suppliers() {
		return $this->hasMany(static::class,'parent_id','id');
	}

	/** ATTRIBUTES **/

	public function getActiveDisplayAttribute($value)
	{
		return sprintf('<span class="btn-sm btn-block btn-%s text-center">%s</span>',$this->active ? 'primary' : 'danger',$this->active ? 'Active' : 'Inactive');
	}

	/**
	 * Logged in users full name
	 *
	 * @return string
	 */
	public function getFullNameAttribute(): string
	{
		return sprintf('%s %s',$this->firstname,$this->lastname);
	}

	/**
	 * A list of all invoices currently unpaid
	 *
	 * @return mixed
	 */
	public function getInvoicesDueAttribute()
	{
		return $this->invoices
			->where('active',TRUE)
			->sortBy('id')
			->transform(function ($item) { if ($item->due > 0) return $item; })
			->reverse()
			->filter();
	}

	/**
	 * Return a Carbon Date if it has a value.
	 *
	 * @param $value
	 * @return Carbon
	 * @throws \Exception
	 * @todo This attribute is not in the schema
	 */
	public function getLastAccessAttribute($value)
	{
		if (! is_null($value))
			return new Carbon($value);
	}

	/**
	 * @deprecated Use static::getFullNameAttribute()
	 * @return mixed
	 */
	public function getNameAttribute()
	{
		return $this->full_name;
	}

	/**
	 * Return a list of the payments that the user has made
	 *
	 * @return mixed
	 * @todo Merge this with payments()
	 */
	public function getPaymentHistoryAttribute()
	{
		return $this->payments
			->sortBy('date_payment')
			->reverse();
	}

	/**
	 * The users active services
	 *
	 * @return mixed
	 */
	public function getServicesActiveAttribute()
	{
		return $this->services
			->filter(function($item)
			{
				return $item->isActive();
			});
	}

	public function getServicesCountHtmlAttribute()
	{
		return sprintf('%s <small>/%s</small>',$this->services->where('active',TRUE)->count(),$this->services->count());
	}

	public function getSurFirstNameAttribute()
	{
		return sprintf('%s, %s',$this->lastname,$this->firstname);
	}

	public function getSwitchUrlAttribute()
	{
		return sprintf('<a href="/a/switch/start/%s"><i class="fa fa-external-link"></i></a>',$this->id);
	}

	public function getUserIdAttribute()
	{
		return sprintf('%02s-%04s',$this->site_id,$this->id);
	}

	public function getUserIdUrlAttribute()
	{
		return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id);
	}

	/**
	 * Users password reset email notification
	 *
	 * @param string $token
	 */
	public function sendPasswordResetNotification($token)
	{
		$this->notify((new ResetPasswordNotification($token))->onQueue('high'));
	}

	/** SCOPES */

	public function scopeActive()
	{
		return $this->where('active',TRUE);
	}

	/**
	 * Search for a record
	 *
	 * @param		$query
	 * @param string $term
	 * @return
	 */
	public function scopeSearch($query,string $term)
	{
		// Build our where clause
		// First Name, Last name
		if (preg_match('/\ /',$term)) {
			[$fn,$ln] = explode(' ',$term,2);

			$query->where(function($query1) use ($fn,$ln,$term) {
				$query1->where(function($query2) use ($fn,$ln) {
					return $query2
						->where('firstname','like','%'.$fn.'%')
						->where('lastname','like','%'.$ln.'%');
				});
			});

		} elseif (is_numeric($term)) {
			$query->where('id','like','%'.$term.'%');

		} elseif (preg_match('/\@/',$term)) {
			$query->where('email','like','%'.$term.'%');

		} else {
			$query
				->Where('firstname','like','%'.$term.'%')
				->orWhere('lastname','like','%'.$term.'%');
		}

		return $query;
	}

	/**
	 * Determine if the user is an admin of the account with $id
	 *
	 * @param $id
	 * @return bool
	 */
	public function isAdmin($id): bool
	{
		return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray());
	}

	/** FUNCTIONS */

	/**
	 * Get a list of accounts for the clients of this user
	 *
	 * @return DatabaseCollection
	 */
	public function all_accounts(): DatabaseCollection
	{
		$result = new DatabaseCollection();
		$clients = $this->all_clients();

		foreach ($clients->pluck('accounts') as $accounts) {
			foreach ($accounts as $o) {
				if (! $o->active)
					continue;

				$result->push($o);
			}
		}

		// Include my accounts
		foreach ($this->accounts as $o) {
			if (! $o->active)
				continue;

			$result->push($o);
		}

		return $result;
	}

	/**
	 * Get a list of clients that this user is responsible for.
	 *
	 * @param int $level
	 * @return Collection
	 */
	public function all_clients($level=0,DatabaseCollection $clients=NULL): DatabaseCollection
	{
		$result = is_null($clients) ? $this->clients : $clients;

		$result
			->filter(function($item) { return $item->active; })
			->transform(function($item) use ($level) { $item->level = $level; return $item; });

		foreach ($result->pluck('clients') as $clients) {
			foreach ($this->all_clients($level+1,$clients) as $o) {
				if (! $o->active)
					continue;

				$result->push($o);
			}
		}

		return $result;
	}

	public function all_client_service_inactive()
	{
		$s = Service::InActive();
		$aa = $this->all_accounts()->pluck('id')->unique()->toArray();

		return $s->get()->filter(function($item) use ($aa) {
			return in_array($item->account_id,$aa);
		});
	}

	/**
	 * List of all this users agents, recursively
	 *
	 * @param int $level
	 * @return Collection
	 */
	public function all_agents($level=0)
	{
		$result = collect();

		foreach ($this->agents as $o) {
			if (! $o->active OR ! $o->agents->count())
				continue;

			$o->level = $level;

			$result->push($o);

			// Include agents of agents
			$result->push($o->all_agents($level+1));
		}

		return $result->flatten();
	}

	/**
	 * Show this user's clients with service movements
	 *
	 * A service movement, is an active service where the status is not ACTIVE
	 *
	 * @return DatabaseCollection
	 */
	public function client_service_movements(): DatabaseCollection
	{
		return Service::active()
			->where('order_status','!=','ACTIVE')
			->whereIN('account_id',$this->all_accounts()->pluck('id')->unique()->toArray())
			->with(['account','product'])
			->get();
	}

	/**
	 * Determine if the logged in user is a reseller or wholesaler
	 *
	 * @return bool
	 */
	public function isReseller(): bool
	{
		return in_array($this->role(),['wholesaler','reseller']);
	}

	/**
	 * Determine if the logged in user is a wholesaler
	 *
	 * @return bool
	 */
	public function isWholesaler(): bool
	{
		return in_array($this->role(),['wholesaler']);
	}

	/**
	 * Get all the items for the next invoice
	 *
	 * @param bool $future
	 * @return DatabaseCollection
	 */
	public function next_invoice_items(bool $future=FALSE): DatabaseCollection
	{
		$result = new DatabaseCollection;

		foreach ($this->services as $o) {
			if ($future) {
				if ($o->invoice_next->subDays(config('app.invoice_inadvance'))->isPast())
					continue;

			} else {
				if ($o->invoice_next->subDays(config('app.invoice_inadvance'))->isFuture())
					continue;
			}

			foreach ($o->next_invoice_items($future) as $oo)
				$result->push($oo);
		}

		$result->load([
			'product.descriptions',
			'service.type',
		]);

		return $result;
	}

	public function role()
	{
		// If I have agents and no parent, I am the wholesaler
		if (is_null($this->parent_id) AND ($this->all_agents()->count() OR $this->all_clients()->count()))
			return 'wholesaler';

		// If I have agents and a parent, I am a reseller
		elseif ($this->parent_id AND ($this->all_agents()->count() OR $this->all_clients()->count()))
			return 'reseller';

		// If I have no agents and a parent, I am a customer
		elseif (! $this->all_agents()->count() AND ! $this->all_clients()->count())
			return 'customer';
	}
}