<?php

namespace App\Models\Inventory;

use App\Models\Branch;
use App\Models\Catalog\Product;
use App\Models\Concerns\Auditable;
use App\Models\Concerns\BelongsToTenant;
use App\Models\Concerns\BranchScoped;
use App\Models\Lookups\Status;
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;

class StockBatch extends Model
{
    use Auditable, BelongsToTenant, BranchScoped, SoftDeletes;

    protected $fillable = [
        'uuid', 'branch_id', 'product_id', 'internal_batch_number',
        'manufacture_date', 'expiry_date',
        'received_quantity', 'available_quantity', 'reserved_quantity', 'damaged_quantity',
        'batch_cost', 'sale_price', 'grn_item_id', 'grn_id', 'status_id', 'notes',
        'created_by', 'updated_by', 'deleted_by',
    ];

    protected function casts(): array
    {
        return [
            'manufacture_date' => 'date',
            'expiry_date' => 'date',
            'received_quantity' => 'decimal:4',
            'available_quantity' => 'decimal:4',
            'reserved_quantity' => 'decimal:4',
            'damaged_quantity' => 'decimal:4',
            'batch_cost' => 'decimal:2',
            'sale_price' => 'decimal:2',
        ];
    }

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

    // ── Relations ─────────────────────────────────────────────────────────────

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

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

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

    public function serials(): HasMany
    {
        return $this->hasMany(ProductSerial::class, 'stock_batch_id');
    }

    public function grn(): BelongsTo
    {
        return $this->belongsTo(Grn::class, 'grn_id');
    }

    public function grnItem(): BelongsTo
    {
        return $this->belongsTo(GrnItem::class, 'grn_item_id');
    }

    // ── Scopes ────────────────────────────────────────────────────────────────

    /** Only active batches (not deactivated, expired, recalled, or locked). */
    public function scopeActive(Builder $query): void
    {
        $query->whereHas('status', fn ($q) => $q->where('code', Status::BATCH_ACTIVE));
    }

    /**
     * Every lot except the ones an operator has parked.
     *
     * The looser counterpart of scopeActive(), for the forms that must still
     * reach a dead lot for a legitimate reason — writing off expired stock,
     * correcting the count on a recalled lot — but must never offer one that
     * has been deactivated. Deactivation is the one status that means "do not
     * put this in front of anyone", so it is the only one filtered here.
     */
    public function scopeNotDeactivated(Builder $query): void
    {
        $query->whereDoesntHave('status', fn ($q) => $q->where('code', Status::BATCH_INACTIVE));
    }

    /** Batches expiring on or before the given date. */
    public function scopeExpiringBefore(Builder $query, \DateTimeInterface|string $date): void
    {
        $query->whereNotNull('expiry_date')->where('expiry_date', '<=', $date);
    }

    /**
     * FEFO order: batches with an expiry date first (soonest expiry first),
     * then non-expiry batches in FIFO (oldest created_at) order.
     */
    public function scopeFefoOrder(Builder $query): void
    {
        $query
            ->orderByRaw('CASE WHEN expiry_date IS NULL THEN 1 ELSE 0 END')
            ->orderBy('expiry_date')
            ->orderBy('created_at');
    }

    /**
     * The sell order of a product's lots, driven by its expiry switch:
     *
     *  - expiry-tracked products sell FEFO — soonest expiry first, lots
     *    without a date (legacy receipts) last, created_at as the tiebreak;
     *  - everything else sells FIFO — oldest receipt (created_at) first.
     *
     * This is the order a barcode-driven sale draws from when no lot was
     * picked by hand. Manual picks go through the lot picker and may choose
     * any lot (with the override permission).
     */
    public function scopeSellOrder(Builder $query, bool $expiryTracked): void
    {
        if ($expiryTracked) {
            $query
                ->orderByRaw('CASE WHEN expiry_date IS NULL THEN 1 ELSE 0 END')
                ->orderBy('expiry_date')
                ->orderBy('created_at');

            return;
        }

        $query->orderBy('created_at');
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

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

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

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

    public function hasAvailableStock(): bool
    {
        return (float) $this->available_quantity > 0;
    }
}
