<?php

namespace App\Domains\Catalog\Models;

use App\Domains\Attachable\Models\Attachment;
use App\Domains\Catalog\Enums\CatalogItemType;
use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Catalog\Enums\InventorySubject;
use App\Domains\Catalog\Services\CatalogInventoryService;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Models\Ticket;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;

#[Fillable([
    'tenant_code',
    'category_id',
    'brand_id',
    'inventory_id',
    'type',
    'slug',
    'nombre',
    'group_order',
    'descripcion',
    'precio',
    'inventory_policy',
    'inventory_subject',
    'max_units_per_user',
    'has_tickets',
])]
class CatalogItem extends Model
{
    use HasFactory, SoftDeletes;

    public $timestamps = false;

    protected $table = 'catalog_items';

    protected $attributes = [
        'type' => CatalogItemType::Standard->value,
        'inventory_policy' => InventoryPolicy::Tracked->value,
        'inventory_subject' => InventorySubject::Product->value,
        'has_tickets' => false,
        'group_order' => 0,
    ];

    protected function casts(): array
    {
        return [
            'category_id' => 'integer',
            'brand_id' => 'integer',
            'inventory_id' => 'integer',
            'type' => CatalogItemType::class,
            'group_order' => 'integer',
            'precio' => 'decimal:2',
            'inventory_policy' => InventoryPolicy::class,
            'inventory_subject' => InventorySubject::class,
            'max_units_per_user' => 'integer',
            'has_tickets' => 'boolean',
        ];
    }

    /** @return BelongsTo<Tenant, $this> */
    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
    }

    /** @return BelongsTo<Category, $this> */
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    /** @return BelongsTo<Brand, $this> */
    public function brand(): BelongsTo
    {
        return $this->belongsTo(Brand::class);
    }

    /** @return BelongsTo<Inventory, $this> */
    public function inventory(): BelongsTo
    {
        return $this->belongsTo(Inventory::class);
    }

    /** @return HasMany<BundleComponent, $this> */
    public function bundleComponents(): HasMany
    {
        return $this->hasMany(BundleComponent::class, 'bundle_catalog_item_id');
    }

    /** @return HasMany<BundleComponent, $this> */
    public function bundleComponentUsages(): HasMany
    {
        return $this->hasMany(BundleComponent::class, 'component_catalog_item_id');
    }

    /** @return HasMany<Variant, $this> */
    public function variants(): HasMany
    {
        return $this->hasMany(Variant::class);
    }

    /** @return HasMany<Ticket, $this> */
    public function sourceTickets(): HasMany
    {
        return $this->hasMany(Ticket::class, 'source_catalog_item_id');
    }

    /** @return BelongsToMany<Attribute, $this> */
    public function attributes(): BelongsToMany
    {
        return $this->belongsToMany(Attribute::class, 'item_attributes')
            ->withTimestamps();
    }

    /** @return HasMany<ItemAttribute, $this> */
    public function itemAttributes(): HasMany
    {
        return $this->hasMany(ItemAttribute::class);
    }

    /** @return HasMany<FeaturedItem, $this> */
    public function featuredItems(): HasMany
    {
        return $this->hasMany(FeaturedItem::class);
    }

    /** @return BelongsToMany<Attachment, $this> */
    public function attachments(): BelongsToMany
    {
        return $this->allAttachments()->wherePivot('is_enabled', true);
    }

    /** @return BelongsToMany<Attachment, $this> */
    public function allAttachments(): BelongsToMany
    {
        return $this->belongsToMany(
            Attachment::class,
            'catalog_items_attachments',
            'catalog_item_id',
            'attachment_id'
        )
            ->withPivot(['orden', 'is_enabled'])
            ->wherePivotNull('variant_id')
            ->orderByPivot('orden');
    }

    public function availableStock(): ?int
    {
        return app(CatalogInventoryService::class)->availableQuantity($this);
    }

    public function isAvailable(): bool
    {
        if ($this->type === CatalogItemType::Bundle) {
            $availableStock = $this->availableStock();

            return $availableStock === null || $availableStock > 0;
        }

        if ($this->inventory_policy === InventoryPolicy::Unlimited) {
            return true;
        }

        return ($this->availableStock() ?? 0) > 0;
    }

    /** @param Builder<CatalogItem> $query */
    public function scopeWhereAvailable(Builder $query): Builder
    {
        return $query->where(function (Builder $query): void {
            $query
                ->where('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value)
                ->orWhereHas(
                    'variants.inventory',
                    fn (Builder $inventoryQuery): Builder => $inventoryQuery
                        ->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
                )
                ->orWhere(function (Builder $directItemQuery): void {
                    $directItemQuery
                        ->whereDoesntHave('variants')
                        ->where(function (Builder $inventoryQuery): void {
                            $inventoryQuery
                                ->whereNull('catalog_items.inventory_id')
                                ->orWhereHas(
                                    'inventory',
                                    fn (Builder $availableInventoryQuery): Builder => $availableInventoryQuery
                                        ->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
                                );
                        });
                });
        });
    }

    /** @return Collection<int, Variant> */
    public function visibleVariants(?int $includedVariantId = null): Collection
    {
        return $this->variants
            ->filter(fn (Variant $variant): bool => ($includedVariantId !== null && $variant->id === $includedVariantId)
                || $this->inventory_policy === InventoryPolicy::Unlimited
                || ($variant->inventory?->availableStock() ?? 0) > 0)
            ->values();
    }

    public function getPrice(): float
    {
        return (float) $this->precio;
    }

    public function getName(): string
    {
        return $this->nombre;
    }

    public function getSelectionLabel(): string
    {
        return $this->getName();
    }

    public function getDescription(): ?string
    {
        return $this->descripcion;
    }

    public function isBundle(): bool
    {
        return $this->type === CatalogItemType::Bundle;
    }
}
