<?php

namespace App\Models\Sales;

use App\Models\Branch;
use App\Models\Concerns\Auditable;
use App\Models\Concerns\BelongsToTenant;
use App\Models\Concerns\BranchScoped;
use App\Models\Customers\Customer;
use App\Models\Customers\DeliveryAddress;
use App\Models\Lookups\Status;
use App\Models\People\SalesRep;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;

/**
 * An order taken from a customer, ahead of any invoice.
 *
 * The order is the promise ("500 of these, plus one custom cake, by the 20th");
 * the invoice is the settlement. They are separate records because they have
 * genuinely different lifecycles: an order can sit in production for weeks,
 * take advance payments, and be edited, none of which an invoice may do.
 * Converting an order writes an Invoice (via SalesOrderService) and stamps
 * `converted_invoice_id` here — the two stay linked forever after.
 */
class SalesOrder extends Model
{
    use Auditable, BelongsToTenant, BranchScoped, SoftDeletes;

    protected $fillable = [
        'uuid', 'order_number',
        'branch_id', 'customer_id', 'delivery_address_id', 'sales_rep_id',
        'status_id', 'payment_status_id',
        'order_date', 'deadline_date',
        'subtotal', 'vat_total', 'discount_type', 'discount_value', 'discount',
        'net_total', 'advance_paid', 'balance',
        'note', 'converted_invoice_id', 'converted_at',
    ];

    protected function casts(): array
    {
        return [
            'order_date' => 'date',
            'deadline_date' => 'date',
            'converted_at' => 'datetime',
            'subtotal' => 'decimal:2',
            'vat_total' => 'decimal:2',
            'discount_value' => 'decimal:2',
            'discount' => 'decimal:2',
            'net_total' => 'decimal:2',
            'advance_paid' => 'decimal:2',
            'balance' => 'decimal:2',
        ];
    }

    protected static function booted(): void
    {
        static::creating(function (self $order): void {
            if (! $order->uuid) {
                $order->uuid = (string) Str::ulid();
            }
        });
    }

    public function items(): HasMany
    {
        return $this->hasMany(SalesOrderItem::class);
    }

    /**
     * Only the made-to-order lines — what the Custom Orders desk actually works from.
     */
    public function customItems(): HasMany
    {
        return $this->items()->where('is_custom', true);
    }

    public function payments(): HasMany
    {
        return $this->hasMany(SalesOrderPayment::class);
    }

    public function branch(): BelongsTo
    {
        return $this->belongsTo(Branch::class, 'branch_id');
    }

    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    public function deliveryAddress(): BelongsTo
    {
        return $this->belongsTo(DeliveryAddress::class);
    }

    public function rep(): BelongsTo
    {
        return $this->belongsTo(SalesRep::class, 'sales_rep_id');
    }

    public function status(): BelongsTo
    {
        return $this->belongsTo(Status::class, 'status_id');
    }

    public function paymentStatus(): BelongsTo
    {
        return $this->belongsTo(Status::class, 'payment_status_id');
    }

    public function invoice(): BelongsTo
    {
        return $this->belongsTo(Invoice::class, 'converted_invoice_id')->withTrashed();
    }

    public function isConverted(): bool
    {
        return $this->converted_invoice_id !== null;
    }

    public function isCancelled(): bool
    {
        return $this->status?->code === Status::SO_CANCELLED;
    }

    /**
     * Whether anything at all may still be done to this order by hand.
     *
     * Distinct from isOpen(): a converted order is closed to conversion,
     * payments and cancellation — the invoice owns the money and the stock —
     * but its lines and totals may still be edited, because the invoice is
     * resynced from them (SalesOrderService::update() →
     * InvoiceService::resyncFromSalesOrder()). Only a cancelled order is
     * fully frozen.
     */
    public function isEditable(): bool
    {
        return ! $this->isCancelled();
    }

    /**
     * A converted or cancelled order is a closed book: its lines and money are
     * now the invoice's business, so nothing here may be converted, paid or
     * cancelled any more. Editing is a separate question — see isEditable().
     */
    public function isOpen(): bool
    {
        return ! $this->isConverted() && ! $this->isCancelled();
    }

    /**
     * Whole days from today to the deadline. Negative when already overdue,
     * null when the order has no deadline or is no longer being worked on
     * (a delivered/cancelled order can't be "late" any more).
     */
    public function daysToDeadline(): ?int
    {
        if (! $this->deadline_date || ! $this->isOpen()) {
            return null;
        }

        return (int) now()->startOfDay()->diffInDays($this->deadline_date->startOfDay(), false);
    }

    /**
     * Orders still being worked on — excludes converted and cancelled ones, so
     * the deadline listing and the due-soon counts never nag about closed work.
     *
     * @param  Builder<SalesOrder>  $query
     * @return Builder<SalesOrder>
     */
    public function scopeOpen(Builder $query): Builder
    {
        return $query
            ->whereNull('converted_invoice_id')
            ->whereHas('status', fn ($q) => $q->where('code', '!=', Status::SO_CANCELLED));
    }
}
