<?php

namespace Daylight\Core\Models;

use App\Models\User;
use Daylight\Core\Models\Enums\AddressType;
use Daylight\Core\Models\Enums\OrderSource;
use Daylight\Core\Models\Enums\OrderStatus;
use Daylight\Core\Models\Enums\PaymentMethod;
use Daylight\Core\Models\Enums\ShippingMethod;
use Daylight\Core\Modules\Payment\Enums\PaymentStatus;
use Illuminate\Contracts\Cache\Lock;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;

class Order extends Model
{
    protected $guarded = [];

    protected $casts = [
        'tax_breakdown' => 'json',
        'source' => OrderSource::class,
        'status' => OrderStatus::class,
        'payment_gateway_status' => PaymentStatus::class,
        'payment_method' => PaymentMethod::class,
        'shipping_method' => ShippingMethod::class,
    ];

    /** Held from before number generation until after INSERT to prevent races. */
    private static ?Lock $numberLock = null;

    protected static function boot(): void
    {
        parent::boot();

        static::creating(function (Order $order) {
            $order->uuid = (string) Str::uuid();

            if (is_null($order->source)) {
                $order->source = OrderSource::STORE;
            }

            if (is_null($order->number) && $order->source === OrderSource::STORE) {
                // Acquire the lock before computing the number so no other process
                // can generate a duplicate. Released in 'created'.
                Order::$numberLock = Cache::lock('daylight:order:number', 30);
                Order::$numberLock->block(10);

                $order->number = daylightModel('order')::generateOrderNumber();
            }
        });

        static::created(function () {
            // Release after the INSERT is committed so any concurrent request
            // that acquires the lock now sees the correct MAX.
            Order::$numberLock?->release();
            Order::$numberLock = null;
        });
    }

    /** Used internally during creation (lock must already be held). */
    private static function nextOrderNumber(): int
    {
        $year = (int) now()->format('Y');
        $offset = (int) config('daylight.orders.number_offset', 1400);
        $count = static::where('source', OrderSource::STORE)
            ->whereYear('created_at', now()->year)
            ->count();

        return (int) ($year . str_pad((string) ($offset + $count + 1), 6, '0', STR_PAD_LEFT));
    }

    /** Public for use when manually reassigning a number (e.g. legacy conflict). */
    public static function generateOrderNumber(): int
    {
        return static::nextOrderNumber();
    }

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

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

    public function items(): HasMany
    {
        return $this->hasMany(daylightModel('order_item'));
    }

    public function addresses(): HasMany
    {
        return $this->hasMany(daylightModel('order_address'));
    }

    public function updates(): HasMany
    {
        return $this->hasMany(daylightModel('order_update'))->latest();
    }

    public function billingAddress(): HasOne
    {
        return $this->addresses()->one()->ofMany([
            'id' => 'max',
        ], function (Builder $builder) {
            $builder->whereType(AddressType::BILLING);
        });
    }

    public function shippingAddress(): HasOne
    {
        return $this->addresses()->one()->ofMany([
            'id' => 'max',
        ], function (Builder $builder) {
            $builder->whereType(AddressType::SHIPPING);
        });
    }

    public function orderNumber(): Attribute
    {
        return Attribute::make(
            get: function () {
                return $this->number;
            }
        );
    }

    public function getTemporaryId(): ?int
    {
        return $this->temporary_id;
    }

    public function getOrderNumber(): string
    {
        return $this->number;
    }

    public function getCustomerNumber(): int
    {
        return $this->customer->imdex_id;
    }

    public function getDeliveryAddressNumber(): int
    {
        return $this->items->first()->deliveryAddress->imdex_id;
    }

    public function getOrderLines(): Collection
    {
        return $this->items;
    }
}
