<?php

namespace Tests\Feature\Catalog;

use App\Domains\Attachable\Enums\AttachmentType;
use App\Domains\Attachable\Models\Attachment;
use App\Domains\Catalog\Enums\CatalogItemType;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Inventory;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Shared\Enums\FieldType;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;

class CatalogServiceTest extends TestCase
{
    use RefreshDatabase;

    private CatalogService $service;

    private Tenant $tenant;

    protected function setUp(): void
    {
        parent::setUp();

        $this->service = app(CatalogService::class);
        $this->tenant = $this->createTenant();
    }

    public function test_it_creates_an_item_with_direct_inventory_when_it_has_no_variants(): void
    {
        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'simple-item',
            'nombre' => 'Simple item',
            'precio' => 100,
            'real_stock' => 12,
        ]);

        $this->assertNotNull($item->inventory_id);
        $this->assertSame(CatalogItemType::Standard, $item->type);
        $this->assertCount(0, $item->variants);
        $this->assertSame(12, $item->inventory->real_stock);
        $this->assertSame(0, $item->inventory->reserved_stock);
        $this->assertSame(0, $item->inventory->sold_units);
    }

    public function test_it_creates_variant_inventory_without_direct_item_inventory(): void
    {
        $attribute = Attribute::query()->create([
            'tenant_codigo' => $this->tenant->codigo,
            'codigo' => 'size',
            'nombre' => 'Size',
            'type' => FieldType::String,
        ]);
        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'variant-item',
            'nombre' => 'Variant item',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
            'variants' => [
                [
                    'real_stock' => 5,
                    'values' => [$attribute->codigo => 'S'],
                ],
                [
                    'real_stock' => 8,
                    'values' => [$attribute->codigo => 'M'],
                ],
            ],
        ]);

        $this->assertNull($item->inventory_id);
        $this->assertCount(2, $item->variants);
        $this->assertCount(1, $item->itemAttributes);
        $this->assertTrue($item->itemAttributes->first()->attribute->is($attribute));
        $this->assertSame([5, 8], $item->variants->pluck('inventory.real_stock')->all());
        $this->assertSame(
            ['S', 'M'],
            $item->variants->pluck('definitions')->flatten()->pluck('value')->all(),
        );

        foreach ($item->variants as $variant) {
            $this->assertNotNull($variant->inventory_id);
            $this->assertSame(0, $variant->inventory->reserved_stock);
            $this->assertSame(0, $variant->inventory->sold_units);
        }
    }

    public function test_it_soft_deletes_an_item_and_its_variants_without_removing_their_sources(): void
    {
        $attribute = $this->createAttribute('size');
        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'archived-item',
            'nombre' => 'Archived item',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
            'variants' => [[
                'real_stock' => 5,
                'values' => [$attribute->codigo => 'M'],
            ]],
        ]);
        $variant = $item->variants->sole();

        $this->service->delete($item);

        $this->assertSoftDeleted('catalog_items', ['id' => $item->id]);
        $this->assertSoftDeleted('variantes', ['id' => $variant->id]);
        $this->assertDatabaseHas('inventories', ['id' => $variant->inventory_id]);
        $this->assertDatabaseHas('variant_values', [
            'variant_id' => $variant->id,
            'value' => 'M',
        ]);
        $this->assertNull(CatalogItem::query()->find($item->id));
        $this->assertNull(Variant::query()->find($variant->id));
        $this->assertNotNull(CatalogItem::withTrashed()->find($item->id));
        $this->assertNotNull(Variant::withTrashed()->find($variant->id));
    }

    public function test_deleting_the_last_variant_soft_deletes_its_catalog_item(): void
    {
        $attribute = $this->createAttribute('size');
        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'last-variant',
            'nombre' => 'Last variant',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
            'variants' => [[
                'real_stock' => 5,
                'values' => [$attribute->codigo => 'M'],
            ]],
        ]);
        $variant = $item->variants->sole();

        $this->service->deleteVariant($variant);

        $this->assertSoftDeleted('variantes', ['id' => $variant->id]);
        $this->assertSoftDeleted('catalog_items', ['id' => $item->id]);
        $this->assertDatabaseHas('inventories', ['id' => $variant->inventory_id]);
    }

    public function test_deleting_a_variant_keeps_the_item_active_when_an_inherited_price_variant_remains(): void
    {
        $attribute = $this->createAttribute('size');
        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'remaining-variant',
            'nombre' => 'Remaining variant',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
            'variants' => [
                [
                    'real_stock' => 5,
                    'values' => [$attribute->codigo => 'M'],
                ],
                [
                    'real_stock' => 5,
                    'values' => [$attribute->codigo => 'L'],
                ],
            ],
        ]);
        $deletedVariant = $item->variants->first();

        $this->service->deleteVariant($deletedVariant);

        $this->assertSoftDeleted('variantes', ['id' => $deletedVariant->id]);
        $this->assertFalse($item->fresh()->trashed());
        $this->assertSame(1, $item->variants()->count());
    }

    public function test_it_can_hide_an_item_attribute_from_the_product_selector(): void
    {
        $attribute = $this->createAttribute('internal_type');

        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'hidden-attribute-item',
            'nombre' => 'Hidden attribute item',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
            'hidden_attribute_codes' => [$attribute->codigo],
            'variants' => [[
                'real_stock' => 5,
                'values' => [$attribute->codigo => 'internal'],
            ]],
        ]);

        $this->assertFalse($item->itemAttributes->sole()->show_in_selector);
    }

    public function test_it_can_configure_an_attribute_label_for_ticket_names(): void
    {
        $attribute = $this->createAttribute('sector');

        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'ticket-labeled-attribute-item',
            'nombre' => 'Ticket labeled attribute item',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
            'ticket_attribute_labels' => [$attribute->codigo => 'Lado'],
            'variants' => [[
                'real_stock' => 5,
                'values' => [$attribute->codigo => 'A'],
            ]],
        ]);

        $this->assertSame('Lado', $item->itemAttributes->sole()->ticket_label);
    }

    public function test_it_allows_the_same_event_date_with_different_attribute_values(): void
    {
        $sector = $this->createAttribute('sector');
        $eventDateAttribute = $this->createAttribute('event_date', FieldType::EventDate);
        $eventDate = $this->tenant->eventDates()->create([
            'date' => '2026-10-09',
            'time_start' => '09:00',
            'time_end' => '18:00',
        ]);

        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'entry-by-sector',
            'nombre' => 'Entry by sector',
            'precio' => 100,
            'attribute_codes' => [$eventDateAttribute->codigo, $sector->codigo],
            'variants' => [
                [
                    'event_date_id' => $eventDate->id,
                    'values' => ['sector' => 'General'],
                ],
                [
                    'event_date_id' => $eventDate->id,
                    'values' => ['sector' => 'VIP'],
                ],
            ],
        ]);

        $this->assertCount(2, $item->variants);
        $this->assertSame(
            [$eventDate->id, $eventDate->id],
            $item->variants->pluck('event_date_id')->all(),
        );
        $this->assertSame(
            ['event_date', 'sector'],
            $item->itemAttributes->pluck('attribute.codigo')->all(),
        );
        $this->assertSame(
            FieldType::EventDate,
            $item->itemAttributes->first()->attribute->type,
        );
    }

    public function test_it_creates_a_variant_with_multiple_event_dates(): void
    {
        $eventDateAttribute = $this->createAttribute('event_date', FieldType::EventDate);
        $firstDate = $this->tenant->eventDates()->create([
            'date' => '2026-10-09',
            'time_start' => '00:00:00',
            'time_end' => '23:59:59',
        ]);
        $secondDate = $this->tenant->eventDates()->create([
            'date' => '2026-10-10',
            'time_start' => '00:00:00',
            'time_end' => '23:59:59',
        ]);

        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'multi-date-pass',
            'nombre' => 'Multi-date pass',
            'precio' => 100,
            'attribute_codes' => [$eventDateAttribute->codigo],
            'multi_select_attribute_codes' => ['event_date'],
            'variants' => [[
                'real_stock' => 5,
                'event_date_ids' => [$secondDate->id, $firstDate->id],
            ]],
        ]);

        $variant = $item->variants->sole();
        $this->assertNull($variant->event_date_id);
        $this->assertSame([$firstDate->id, $secondDate->id], $variant->eventDates->pluck('id')->all());
        $this->assertSame(
            [(string) $firstDate->id, (string) $secondDate->id],
            $variant->selectionValues()->get('event_date'),
        );
    }

    public function test_it_creates_multiple_values_for_any_multi_select_attribute(): void
    {
        $color = $this->createAttribute('color', FieldType::Select);
        $color->options()->createMany([
            ['value' => 'Green', 'label' => 'Green', 'sort_order' => 1],
            ['value' => 'White', 'label' => 'White', 'sort_order' => 2],
        ]);

        $item = $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'multi-color-shirt',
            'nombre' => 'Multi-color shirt',
            'precio' => 100,
            'attribute_codes' => ['color'],
            'multi_select_attribute_codes' => ['color'],
            'variants' => [[
                'real_stock' => 5,
                'values' => ['color' => ['White', 'Green']],
            ]],
        ]);

        $variant = $item->variants->sole();
        $this->assertSame(['White', 'Green'], $variant->definitions->pluck('value')->all());
        $this->assertSame(['White', 'Green'], $variant->selectionValues()->get('color'));
    }

    public function test_it_rejects_duplicate_variant_combinations(): void
    {
        $sector = $this->createAttribute('sector');
        $eventDateAttribute = $this->createAttribute('event_date', FieldType::EventDate);
        $eventDate = $this->tenant->eventDates()->create([
            'date' => '2026-10-09',
            'time_start' => '09:00',
            'time_end' => '18:00',
        ]);

        try {
            $this->service->create([
                'tenant_code' => $this->tenant->codigo,
                'slug' => 'duplicate-entry',
                'nombre' => 'Duplicate entry',
                'precio' => 100,
                'attribute_codes' => [$eventDateAttribute->codigo, $sector->codigo],
                'variants' => [
                    ['event_date_id' => $eventDate->id, 'values' => ['sector' => 'VIP']],
                    ['event_date_id' => $eventDate->id, 'values' => ['sector' => ' vip ']],
                ],
            ]);

            $this->fail('A validation exception was not thrown.');
        } catch (ValidationException $exception) {
            $this->assertArrayHasKey('variants.1', $exception->errors());
        }
    }

    public function test_it_rejects_direct_inventory_together_with_variants(): void
    {
        $attribute = $this->createAttribute('size');

        try {
            $this->service->create([
                'tenant_code' => $this->tenant->codigo,
                'slug' => 'invalid-item',
                'nombre' => 'Invalid item',
                'precio' => 100,
                'real_stock' => 10,
                'attribute_codes' => [$attribute->codigo],
                'variants' => [
                    ['real_stock' => 5],
                ],
            ]);

            $this->fail('A validation exception was not thrown.');
        } catch (ValidationException $exception) {
            $this->assertArrayHasKey('real_stock', $exception->errors());
        }

        $this->assertDatabaseMissing('catalog_items', ['slug' => 'invalid-item']);
        $this->assertSame(0, CatalogItem::query()->count());
        $this->assertSame(0, Variant::query()->count());
        $this->assertSame(0, Inventory::query()->count());
    }

    public function test_it_rejects_variants_when_attribute_codes_are_empty(): void
    {
        $this->expectException(ValidationException::class);

        $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'variants-without-attributes',
            'nombre' => 'Variants without attributes',
            'precio' => 100,
            'variants' => [
                ['real_stock' => 5],
            ],
        ]);
    }

    public function test_it_requires_variants_when_attribute_codes_are_present(): void
    {
        $attribute = $this->createAttribute('color');

        $this->expectException(ValidationException::class);

        $this->service->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'attributes-without-variants',
            'nombre' => 'Attributes without variants',
            'precio' => 100,
            'attribute_codes' => [$attribute->codigo],
        ]);
    }

    public function test_it_only_resolves_attribute_codes_from_the_item_tenant(): void
    {
        $otherTenant = $this->createTenant('other-tenant');
        Attribute::query()->create([
            'tenant_codigo' => $otherTenant->codigo,
            'codigo' => 'size',
            'nombre' => 'Size',
            'type' => FieldType::String,
        ]);

        try {
            $this->service->create([
                'tenant_code' => $this->tenant->codigo,
                'slug' => 'foreign-attribute',
                'nombre' => 'Foreign attribute',
                'precio' => 100,
                'attribute_codes' => ['size'],
                'variants' => [
                    [
                        'real_stock' => 5,
                        'values' => ['size' => 'M'],
                    ],
                ],
            ]);

            $this->fail('A validation exception was not thrown.');
        } catch (ValidationException $exception) {
            $this->assertArrayHasKey('attribute_codes', $exception->errors());
        }

        $this->assertDatabaseMissing('catalog_items', ['slug' => 'foreign-attribute']);
        $this->assertSame(0, Inventory::query()->count());
    }

    private function createTenant(string $code = 'catalog-service'): Tenant
    {
        $headerLogo = $this->createAttachment("{$code}-header");
        $footerLogo = $this->createAttachment("{$code}-footer");

        return Tenant::query()->create([
            'codigo' => $code,
            'nombre' => ucfirst($code),
            'dominio' => "{$code}.local",
            'primary_color' => '#000000',
            'secondary_color' => '#000000',
            'danger_color' => '#000000',
            'success_color' => '#000000',
            'header_bg_color' => '#000000',
            'footer_bg_color' => '#000000',
            'header_logo_id' => $headerLogo->id,
            'footer_logo_id' => $footerLogo->id,
        ]);
    }

    private function createAttachment(string $name): Attachment
    {
        return Attachment::query()->create([
            'path' => "test/{$name}.png",
            'filename' => "{$name}.png",
            'type' => AttachmentType::Image,
            'mime_type' => 'image/png',
        ]);
    }

    private function createAttribute(
        string $code,
        FieldType $type = FieldType::String,
    ): Attribute {
        return Attribute::query()->create([
            'tenant_codigo' => $this->tenant->codigo,
            'codigo' => $code,
            'nombre' => ucfirst($code),
            'type' => $type,
        ]);
    }
}
