<?php

namespace Tests\Unit\Catalog;

use App\Domains\Attachable\Models\Attachment;
use App\Domains\Catalog\Enums\CatalogItemType;
use App\Domains\Catalog\Enums\FeaturedGroupSource;
use App\Domains\Catalog\Enums\GroupLayout;
use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Catalog\Enums\InventorySubject;
use App\Domains\Catalog\Enums\ProductLayout;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\AttributeOption;
use App\Domains\Catalog\Models\Brand;
use App\Domains\Catalog\Models\BundleComponent;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Category;
use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Catalog\Models\FeaturedItem;
use App\Domains\Catalog\Models\Inventory;
use App\Domains\Catalog\Models\ItemAttribute;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Catalog\Models\VariantDefinition;
use App\Domains\Event\Models\EventDate;
use App\Domains\Shared\Enums\FieldType;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Models\Ticket;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Tests\TestCase;

class CatalogModelsTest extends TestCase
{
    public function test_group_layout_has_all_supported_values(): void
    {
        $this->assertSame([
            'paginated',
            'simple',
            'simple_vertical',
            'carousel',
            'single',
        ], GroupLayout::values());
    }

    public function test_product_layout_has_all_supported_values(): void
    {
        $this->assertSame([
            'row',
            'column_with_image',
            'column_with_cart',
            'ticket_selector',
        ], ProductLayout::values());
    }

    public function test_attribute_maps_its_values_and_relations(): void
    {
        $attribute = new Attribute;
        $attribute->setRawAttributes([
            'is_required' => 1,
            'metadata_schema' => '{"swatch":true}',
            'type' => FieldType::Select->value,
        ]);

        $this->assertSame('attribute', $attribute->getTable());
        $this->assertTrue($attribute->is_required);
        $this->assertSame(['swatch' => true], $attribute->metadata_schema);
        $this->assertSame(FieldType::Select, $attribute->type);
        $this->assertInstanceOf(Tenant::class, $attribute->tenant()->getRelated());
        $this->assertInstanceOf(AttributeOption::class, $attribute->options()->getRelated());
    }

    public function test_event_date_is_an_attribute_type_with_dynamic_options(): void
    {
        $attribute = new Attribute;
        $attribute->type = FieldType::EventDate->value;

        $this->assertSame(FieldType::EventDate, $attribute->type);
        $this->assertTrue($attribute->type->supportsOptions());
        $this->assertTrue($attribute->type->usesDynamicOptions());
        $this->assertContains('event_date', FieldType::values());
        $this->assertInstanceOf(EventDate::class, $attribute->eventDates()->getRelated());
    }

    public function test_catalog_item_is_the_catalog_root(): void
    {
        $item = new CatalogItem;
        $this->assertSame(0, $item->group_order);
        $item->setRawAttributes([
            'category_id' => '10',
            'brand_id' => '20',
            'inventory_id' => '30',
            'type' => CatalogItemType::Standard->value,
            'group_order' => '4',
            'precio' => '12.50',
            'inventory_policy' => InventoryPolicy::Tracked->value,
            'inventory_subject' => InventorySubject::Seat->value,
            'has_tickets' => 1,
        ]);

        $this->assertSame('catalog_items', $item->getTable());
        $this->assertFalse($item->usesTimestamps());
        $this->assertSame(10, $item->category_id);
        $this->assertSame(20, $item->brand_id);
        $this->assertSame(30, $item->inventory_id);
        $this->assertSame(CatalogItemType::Standard, $item->type);
        $this->assertSame(4, $item->group_order);
        $this->assertSame('12.50', $item->precio);
        $this->assertSame(InventoryPolicy::Tracked, $item->inventory_policy);
        $this->assertSame(InventorySubject::Seat, $item->inventory_subject);
        $this->assertTrue($item->has_tickets);
        $this->assertInstanceOf(Tenant::class, $item->tenant()->getRelated());
        $this->assertInstanceOf(Category::class, $item->category()->getRelated());
        $this->assertInstanceOf(Brand::class, $item->brand()->getRelated());
        $this->assertInstanceOf(Inventory::class, $item->inventory()->getRelated());
        $this->assertInstanceOf(BundleComponent::class, $item->bundleComponents()->getRelated());
        $this->assertInstanceOf(BundleComponent::class, $item->bundleComponentUsages()->getRelated());
        $this->assertInstanceOf(Variant::class, $item->variants()->getRelated());
        $this->assertInstanceOf(Ticket::class, $item->sourceTickets()->getRelated());
        $this->assertInstanceOf(Attribute::class, $item->attributes()->getRelated());
        $this->assertInstanceOf(ItemAttribute::class, $item->itemAttributes()->getRelated());
        $this->assertInstanceOf(FeaturedItem::class, $item->featuredItems()->getRelated());
        $this->assertInstanceOf(Attachment::class, $item->attachments()->getRelated());
        $this->assertSame('catalog_items_attachments', $item->attachments()->getTable());
    }

    public function test_featured_models_map_catalog_relations_and_layout(): void
    {
        $group = new FeaturedGroup;
        $group->setRawAttributes([
            'source_type' => FeaturedGroupSource::Category->value,
            'category_id' => '4',
            'product_layout' => ProductLayout::ColumnWithImage->value,
            'group_layout' => GroupLayout::SimpleVertical->value,
            'group_order' => '2',
        ]);
        $featuredItem = new FeaturedItem;
        $featuredItem->setRawAttributes([
            'featured_group_id' => '10',
            'catalog_item_id' => '20',
            'order' => '3',
        ]);

        $this->assertSame('featured_groups', $group->getTable());
        $this->assertFalse($group->usesTimestamps());
        $this->assertSame(ProductLayout::ColumnWithImage, $group->product_layout);
        $this->assertSame(FeaturedGroupSource::Category, $group->source_type);
        $this->assertSame(4, $group->category_id);
        $this->assertSame(GroupLayout::SimpleVertical, $group->group_layout);
        $this->assertSame(2, $group->group_order);
        $this->assertInstanceOf(Tenant::class, $group->tenant()->getRelated());
        $this->assertInstanceOf(Category::class, $group->category()->getRelated());
        $this->assertInstanceOf(FeaturedItem::class, $group->featuredItems()->getRelated());

        $this->assertSame('featured_items', $featuredItem->getTable());
        $this->assertFalse($featuredItem->usesTimestamps());
        $this->assertSame(10, $featuredItem->featured_group_id);
        $this->assertSame(20, $featuredItem->catalog_item_id);
        $this->assertSame(3, $featuredItem->order);
        $this->assertInstanceOf(FeaturedGroup::class, $featuredItem->featuredGroup()->getRelated());
        $this->assertInstanceOf(CatalogItem::class, $featuredItem->catalogItem()->getRelated());
    }

    public function test_variant_has_direct_catalog_and_inventory_relations(): void
    {
        $variant = new Variant;
        $variant->setRawAttributes([
            'catalog_item_id' => '10',
            'event_date_id' => '15',
            'inventory_id' => '20',
        ]);

        $this->assertSame('variantes', $variant->getTable());
        $this->assertSame(10, $variant->catalog_item_id);
        $this->assertSame(20, $variant->inventory_id);
        $this->assertSame(15, $variant->event_date_id);
        $this->assertInstanceOf(CatalogItem::class, $variant->catalogItem()->getRelated());
        $this->assertInstanceOf(Inventory::class, $variant->inventory()->getRelated());
        $this->assertInstanceOf(EventDate::class, $variant->eventDate()->getRelated());
        $this->assertInstanceOf(Ticket::class, $variant->sourceTickets()->getRelated());
        $this->assertInstanceOf(VariantDefinition::class, $variant->definitions()->getRelated());
        $this->assertInstanceOf(Attachment::class, $variant->attachments()->getRelated());
        $this->assertSame('catalog_items_attachments', $variant->attachments()->getTable());
    }

    public function test_event_date_identifies_a_variant_without_catalog_attributes(): void
    {
        $item = new CatalogItem;
        $item->nombre = 'Entrada General';

        $eventDate = new EventDate;
        $eventDate->date = '2026-10-09';
        $eventDate->time_start = '09:00:00';
        $eventDate->time_end = '18:00:00';

        $variant = new Variant;
        $variant->setRelation('catalogItem', $item);
        $variant->setRelation('eventDate', $eventDate);
        $variant->setRelation('definitions', new EloquentCollection);

        $this->assertSame('Entrada General', $variant->getName());
        $this->assertSame('2026-10-09', $variant->eventDate->date->format('Y-m-d'));
    }

    public function test_variant_exposes_selection_values_with_api_labels(): void
    {
        $attribute = new Attribute;
        $attribute->codigo = 'size';
        $attribute->setRelation('options', new EloquentCollection([
            new AttributeOption(['value' => 'M', 'label' => 'Medium']),
        ]));

        $itemAttribute = new ItemAttribute;
        $itemAttribute->allow_multi_select = false;
        $itemAttribute->setRelation('attribute', $attribute);

        $definition = new VariantDefinition(['value' => 'M']);
        $definition->item_attribute_id = 1;
        $definition->setRelation('itemAttribute', $itemAttribute);

        $eventDate = new EventDate([
            'date' => '2026-10-09',
            'time_start' => '09:00:00',
            'time_end' => '18:00:00',
        ]);
        $eventDate->id = 20;

        $variant = new Variant;
        $variant->setRelation('definitions', new EloquentCollection([$definition]));
        $variant->setRelation('eventDates', new EloquentCollection([$eventDate]));

        $this->assertSame(
            ['value' => 'M', 'label' => 'Medium'],
            $variant->selectionOptions()->get('size'),
        );
        $this->assertSame(
            ['value' => '20', 'label' => '09/10/2026'],
            $variant->selectionOptions()->get('event_date'),
        );
    }

    public function test_variant_orders_selection_options_by_item_order_and_attribute_label(): void
    {
        $definitions = collect([
            ['id' => 1, 'code' => 'zeta', 'label' => 'Zeta', 'sort_order' => 2],
            ['id' => 2, 'code' => 'priority', 'label' => 'Priority', 'sort_order' => 1],
            ['id' => 3, 'code' => 'alpha', 'label' => 'Alpha', 'sort_order' => 2],
        ])->map(function (array $data): VariantDefinition {
            $attribute = new Attribute([
                'codigo' => $data['code'],
                'nombre' => $data['label'],
            ]);
            $attribute->setRelation('options', new EloquentCollection);

            $itemAttribute = new ItemAttribute([
                'sort_order' => $data['sort_order'],
                'allow_multi_select' => false,
            ]);
            $itemAttribute->id = $data['id'];
            $itemAttribute->setRelation('attribute', $attribute);

            $definition = new VariantDefinition(['value' => $data['code']]);
            $definition->item_attribute_id = $itemAttribute->id;
            $definition->setRelation('itemAttribute', $itemAttribute);

            return $definition;
        });

        $variant = new Variant;
        $variant->setRelation('definitions', new EloquentCollection($definitions));
        $variant->setRelation('eventDates', new EloquentCollection);

        $this->assertSame(
            ['priority', 'alpha', 'zeta'],
            $variant->selectionOptions()->keys()->all(),
        );
    }

    public function test_variant_excludes_hidden_attributes_from_selector_options(): void
    {
        $attribute = new Attribute(['codigo' => 'event_date', 'nombre' => 'Fecha']);
        $attribute->setRelation('options', new EloquentCollection);

        $itemAttribute = new ItemAttribute([
            'allow_multi_select' => true,
            'show_in_selector' => false,
        ]);
        $itemAttribute->id = 1;
        $itemAttribute->setRelation('attribute', $attribute);

        $eventDate = new EventDate(['date' => '2026-10-09']);
        $eventDate->id = 20;

        $variant = new Variant;
        $variant->setRelation('definitions', new EloquentCollection);
        $variant->setRelation('eventDates', new EloquentCollection([$eventDate]));

        $this->assertSame(
            [],
            $variant->selectorOptions(new EloquentCollection([$itemAttribute]))->all(),
        );
    }

    public function test_inventory_maps_stock_without_a_polymorphic_owner(): void
    {
        $inventory = $this->trackedInventory(realStock: 10, reservedStock: 3);
        $inventory->sold_units = '2';

        $this->assertSame('inventories', $inventory->getTable());
        $this->assertFalse($inventory->usesTimestamps());
        $this->assertSame(2, $inventory->sold_units);
        $this->assertSame(7, $inventory->availableStock());
        $this->assertInstanceOf(CatalogItem::class, $inventory->catalogItem()->getRelated());
        $this->assertInstanceOf(Variant::class, $inventory->variant()->getRelated());
    }

    public function test_catalog_item_aggregates_variant_inventory(): void
    {
        $first = (new Variant)->setRelation('inventory', $this->trackedInventory(10, 3));
        $second = (new Variant)->setRelation('inventory', $this->trackedInventory(5, 1));
        $item = new CatalogItem;
        $item->inventory_policy = InventoryPolicy::Tracked;
        $item->setRelation('variants', new EloquentCollection([$first, $second]));
        $item->setRelation('inventory', null);

        $this->assertSame(11, $item->availableStock());
        $this->assertTrue($item->isAvailable());
    }

    public function test_catalog_item_only_exposes_available_tracked_variants(): void
    {
        $unavailable = (new Variant)->setRelation('inventory', $this->trackedInventory(3, 3));
        $available = (new Variant)->setRelation('inventory', $this->trackedInventory(5, 2));
        $unavailable->id = 10;
        $available->id = 20;
        $item = new CatalogItem;
        $item->inventory_policy = InventoryPolicy::Tracked;
        $item->setRelation('variants', new EloquentCollection([$unavailable, $available]));

        $this->assertSame([$available], $item->visibleVariants()->all());
        $this->assertSame(
            [$unavailable, $available],
            $item->visibleVariants($unavailable->id)->all(),
        );

        $item->inventory_policy = InventoryPolicy::Unlimited;

        $this->assertSame([$unavailable, $available], $item->visibleVariants()->all());
    }

    public function test_catalog_item_prioritizes_its_inventory_over_variants(): void
    {
        $item = new CatalogItem;
        $item->inventory_policy = InventoryPolicy::Tracked;
        $item->setRelation('variants', new EloquentCollection([
            (new Variant)->setRelation('inventory', $this->trackedInventory(100, 0)),
        ]));
        $item->setRelation('inventory', $this->trackedInventory(8, 2));

        $this->assertSame(6, $item->availableStock());
        $this->assertTrue($item->isAvailable());
    }

    public function test_item_attribute_and_variant_definition_use_catalog_keys(): void
    {
        $itemAttribute = new ItemAttribute;
        $definition = new VariantDefinition;

        $this->assertSame('item_attributes', $itemAttribute->getTable());
        $this->assertInstanceOf(CatalogItem::class, $itemAttribute->catalogItem()->getRelated());
        $this->assertInstanceOf(Attribute::class, $itemAttribute->attribute()->getRelated());
        $this->assertInstanceOf(VariantDefinition::class, $itemAttribute->variantDefinitions()->getRelated());
        $this->assertSame('variant_values', $definition->getTable());
        $this->assertInstanceOf(Variant::class, $definition->variant()->getRelated());
        $this->assertInstanceOf(ItemAttribute::class, $definition->itemAttribute()->getRelated());
    }

    private function trackedInventory(int $realStock, int $reservedStock): Inventory
    {
        $inventory = new Inventory;
        $inventory->setRawAttributes([
            'real_stock' => $realStock,
            'reserved_stock' => $reservedStock,
        ]);

        return $inventory;
    }
}
