<?php

namespace App\Modules\Invoices;

use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\ValidationModel;
use App\Modules\Bookings\Task;
use App\Modules\Bookings\Project;
use App\Models\User;
use App\Modules\Clients\Client;
use App\Modules\Clients\Customer;
use App\Modules\Invoices\InvoiceItem;
use App\Modules\Invoices\InvoiceTransaction;
use App\Modules\Invoices\InvoiceEmail;
use App\Traits\SetCreatorAndUpdatorTrait;

abstract class AbstractInvoice extends ValidationModel
{
    use SoftDeletes;
    use \Venturecraft\Revisionable\RevisionableTrait;
    use SetCreatorAndUpdatorTrait;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['client_id', 'client_type', 'invoice_number', 'invoiced_at', 'task_id', 'project_id', 'total_price', 'include_vat', 'vat', 'discount', 'refunded', 'paid', 'invoice_date', 'converted_at', 'created_by', 'xero_invoice_id', 'xero_synced_at', 'xero_sync_error'];

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'invoices';

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = ['total_paid', 'amount_due', 'net_price'];

    /**
     * The attributes that should be mutated to dates.
     *
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * Array of validation rules.
     *
     * @var  array
     */
    protected static $rules = [
        'client_id'    => 'required',
        'invoice_date' => 'required',
    ];

    /**
     * Boot the model and set up the morphMap for polymorphic relationships.
     *
     * @return void
     */
    public static function boot()
    {
        parent::boot();
        
        // Set up morphMap for polymorphic relationships
        \Illuminate\Database\Eloquent\Relations\Relation::morphMap([
            'client' => Client::class,
            'customer' => Customer::class,
        ]);
    }

    public function items()
    {
        return $this->hasMany(InvoiceItem::class, 'invoice_id');
    }

    public function task()
    {
        return $this->belongsTo(Task::class, 'task_id');
    }

    public function project()
    {
        return $this->belongsTo(Project::class, 'project_id');
    }

    public function transactions()
    {
        return $this->hasMany(InvoiceTransaction::class, 'invoice_id');
    }

    public function isPaid()
    {
        return ($this->exists && $this->total_paid >= $this->getTotalPrice()) ? true : false;
    }

    public function getTotalPrice()
    {
        // Do not bother calculating the items price if the invoice doesn't actually exist
        if (!$this->exists) {
            return 0;
        }

        // If items are already loaded, use the collection
        if ($this->relationLoaded('items')) {
            $total = $this->items->sum(function($item) {
                return $item->price_sold_exc * $item->quantity;
            });
        } else {
            // If items aren't loaded, use a single efficient query
            $total = $this->items()
                ->selectRaw('SUM(price_sold_exc * quantity) as total')
                ->value('total') ?? 0;
        }

        if (!$this->include_vat) {
            $total *= $this->vat / 100 + 1;
        }

        return round($total, 2);
    }

    public function getTotalPaidAttribute()
    {
        if (!$this->exists) {
            return 0;
        }

        // If transactions are already loaded, use the collection
        if ($this->relationLoaded('transactions')) {
            return round($this->transactions
                ->where('type', 'debit')
                ->sum('amount'), 2);
        }

        // If not loaded, use a single efficient query
        return round($this->transactions()
            ->where('type', 'debit')
            ->sum('amount'), 2);
    }

    public function getAmountDueAttribute()
    {
        // Use total_price from database if it's set
        $totalPrice = $this->total_price ?? $this->getTotalPrice();
        return round($totalPrice - $this->total_paid, 2);
    }

    public function getNetPriceAttribute()
    {
        // Use total_price from database if it's set
        $totalPrice = $this->total_price ?? $this->getTotalPrice();
        return round($totalPrice / ($this->vat / 100 + 1), 2);
    }

    public function creator()
    {
        return $this->belongsTo(User::class, 'created_by');
    }

    public function emails()
    {
        return $this->hasMany(InvoiceEmail::class, 'invoice_id');
    }

    /**
     * Get the client (polymorphic relationship).
     * This can be either a Client or Customer model.
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function client()
    {
        return $this->morphTo('client', 'client_type', 'client_id');
    }

    /**
     * Search invoices by a given term.
     *
     * @param  $query
     * @param  $term
     *
     * @return Illuminate\Database\Query\Builder
     */
    public function scopeSearch($query, $term)
    {
        return $query->select('id', 'client_id', 'client_type', 'type', 'invoice_number')->where(function ($q) use ($term) {
                return $q->where('id', 'like', $term.'%')->orWhere('invoice_number', 'like', $term.'%');
            });
    }

    /**
     * Check if this invoice needs to be sent to Xero
     *
     * @return bool
     */
    public function needsXeroSync()
    {
        return empty($this->xero_invoice_id);
    }

    /**
     * Check if this invoice is synced with Xero
     *
     * @return bool
     */
    public function isSyncedWithXero()
    {
        return !empty($this->xero_invoice_id);
    }

    /**
     * Get Xero sync status information
     *
     * @return array
     */
    public function getXeroSyncStatus()
    {
        if ($this->isSyncedWithXero()) {
            return [
                'synced' => true,
                'xero_id' => $this->xero_invoice_id,
                'synced_at' => $this->xero_synced_at,
                'error' => null
            ];
        } elseif (!empty($this->xero_sync_error)) {
            return [
                'synced' => false,
                'xero_id' => null,
                'synced_at' => null,
                'error' => $this->xero_sync_error
            ];
        } else {
            return [
                'synced' => false,
                'xero_id' => null,
                'synced_at' => null,
                'error' => null
            ];
        }
    }
}
