<?php

namespace Tests\Feature\Catalog;

use App\Domains\Attachable\Enums\AttachmentType;
use App\Domains\Attachable\Models\Attachment;
use App\Domains\Auth\Models\User;
use App\Domains\Cart\Models\Cart;
use App\Domains\Cart\Resources\CartItemResource;
use App\Domains\Catalog\Enums\CatalogItemType;
use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Services\CatalogInventoryService;
use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Purchase\Services\CheckoutService;
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 BundleCatalogItemTest extends TestCase
{
    use RefreshDatabase;

    private CatalogService $catalogService;

    private CatalogInventoryService $inventoryService;

    private Tenant $tenant;

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

        $this->catalogService = app(CatalogService::class);
        $this->inventoryService = app(CatalogInventoryService::class);
        $this->tenant = $this->createTenant('bundle-tenant');
    }

    public function test_it_creates_a_bundle_and_derives_its_inventory(): void
    {
        $shirt = $this->createStandardItem('shirt', 11);
        $cap = $this->createStandardItem('cap', 3);
        $bundle = $this->createBundle('training-kit', [
            ['catalog_item_id' => $shirt->id, 'quantity' => 2],
            ['catalog_item_id' => $cap->id, 'quantity' => 1],
        ]);

        $this->assertSame(CatalogItemType::Bundle, $bundle->type);
        $this->assertNull($bundle->inventory_id);
        $this->assertNull($bundle->inventory_policy);
        $this->assertCount(2, $bundle->bundleComponents);
        $this->assertSame(3, $bundle->availableStock());

        $this->inventoryService->reserve($bundle, 2);
        $this->assertSame(4, $shirt->inventory->fresh()->reserved_stock);
        $this->assertSame(2, $cap->inventory->fresh()->reserved_stock);

        $this->inventoryService->release($bundle, 1);
        $this->inventoryService->commit($bundle, 1);

        $this->assertDatabaseHas('inventories', [
            'id' => $shirt->inventory_id,
            'real_stock' => 9,
            'reserved_stock' => 0,
            'sold_units' => 2,
        ]);
        $this->assertDatabaseHas('inventories', [
            'id' => $cap->inventory_id,
            'real_stock' => 2,
            'reserved_stock' => 0,
            'sold_units' => 1,
        ]);
    }

    public function test_bundle_inventory_rolls_back_when_one_component_has_insufficient_stock(): void
    {
        $available = $this->createStandardItem('available', 10);
        $scarce = $this->createStandardItem('scarce', 1);
        $bundle = $this->createBundle('invalid-reservation', [
            ['catalog_item_id' => $available->id, 'quantity' => 2],
            ['catalog_item_id' => $scarce->id, 'quantity' => 1],
        ]);

        try {
            $this->inventoryService->reserve($bundle, 2);
            $this->fail('The reservation should have failed.');
        } catch (\InvalidArgumentException) {
            $this->assertSame(0, $available->inventory->fresh()->reserved_stock);
            $this->assertSame(0, $scarce->inventory->fresh()->reserved_stock);
        }
    }

    public function test_bundle_supports_fixed_variants_and_unlimited_components(): void
    {
        $attribute = Attribute::query()->create([
            'tenant_codigo' => $this->tenant->codigo,
            'codigo' => 'size',
            'nombre' => 'Size',
            'type' => FieldType::String,
        ]);
        $variantItem = $this->catalogService->create([
            'tenant_code' => $this->tenant->codigo,
            'slug' => 'variant-component',
            'nombre' => 'Variant component',
            'precio' => 20,
            'attribute_codes' => [$attribute->codigo],
            'variants' => [
                ['real_stock' => 6, 'values' => ['size' => 'M']],
            ],
        ]);
        $unlimited = $this->createStandardItem(
            'unlimited-component',
            0,
            inventoryPolicy: InventoryPolicy::Unlimited,
        );

        try {
            $this->createBundle('variant-without-selection', [
                ['catalog_item_id' => $variantItem->id, 'quantity' => 1],
            ]);
            $this->fail('A variant component should require variant_id.');
        } catch (ValidationException $exception) {
            $this->assertArrayHasKey('components.0.variant_id', $exception->errors());
        }

        $variant = $variantItem->variants->firstOrFail();
        $bundle = $this->createBundle('mixed-inventory-bundle', [
            [
                'catalog_item_id' => $variantItem->id,
                'variant_id' => $variant->id,
                'quantity' => 2,
            ],
            ['catalog_item_id' => $unlimited->id, 'quantity' => 5],
        ]);

        $this->assertSame(3, $bundle->availableStock());
        $this->inventoryService->reserve($bundle, 2);
        $this->assertSame(4, $variant->inventory->fresh()->reserved_stock);
        $this->assertSame(10, $unlimited->inventory->fresh()->reserved_stock);

        $unlimitedOnly = $this->createBundle('unlimited-bundle', [
            ['catalog_item_id' => $unlimited->id, 'quantity' => 2],
        ]);
        $this->assertNull($unlimitedOnly->availableStock());
        $this->assertTrue($unlimitedOnly->isAvailable());
    }

    public function test_cart_treats_the_bundle_as_a_single_catalog_item(): void
    {
        $originalItem = $this->createStandardItem('original', 10);
        $bundle = $this->createBundle('cart-kit', [
            ['catalog_item_id' => $originalItem->id, 'quantity' => 1],
        ]);
        $cart = Cart::query()->create([
            'tenant_codigo' => $this->tenant->codigo,
            'guest_token' => 'bundle-guest',
            'status' => 'active',
        ]);

        $cartItem = $cart->addItem($bundle->id, null, 1);
        $this->assertSame($bundle->id, $cartItem->catalog_item_id);
        $this->assertNull($cartItem->variant_id);
        $this->assertArrayNotHasKey(
            'components',
            CartItemResource::make($cartItem->load('catalogItem.attachments'))->resolve(),
        );

        $cart->updateItem($cartItem->id, 2);

        $this->assertSame(2, $originalItem->inventory->fresh()->reserved_stock);

        $cart->removeItem($cartItem->id);
        $this->assertSame(0, $originalItem->inventory->fresh()->reserved_stock);
    }

    public function test_checkout_snapshots_only_the_bundle_and_commits_component_inventory(): void
    {
        $component = $this->createStandardItem('checkout-component', 10);
        $bundle = $this->createBundle('checkout-kit', [
            ['catalog_item_id' => $component->id, 'quantity' => 2],
        ], '100.00');
        $user = User::factory()->create();
        $cart = Cart::query()->create([
            'tenant_codigo' => $this->tenant->codigo,
            'user_id' => $user->id,
            'status' => 'active',
        ]);
        $cart->addItem($bundle->id, null, 2);

        $checkoutService = app(CheckoutService::class);
        $purchase = $checkoutService->startCheckout($this->tenant, $user->id, [
            'cart_id' => $cart->id,
            'dni' => '12345678',
            'telefono' => '+54 9 341 555-0000',
            'nombre_apellido' => 'Bundle Buyer',
            'email' => 'bundle@example.com',
        ]);
        $purchase->update(['payment_method' => 'transfer']);
        $checkoutService->confirmPurchase($purchase);

        $this->assertDatabaseCount('compra_items', 1);
        $this->assertDatabaseHas('compra_items', [
            'compra_id' => $purchase->id,
            'source_catalog_item_id' => $bundle->id,
            'source_variant_id' => null,
            'nombre' => $bundle->nombre,
            'cantidad' => 2,
            'precio_unitario' => '100.00',
            'total' => '200.00',
        ]);
        $this->assertDatabaseHas('inventories', [
            'id' => $component->inventory_id,
            'real_stock' => 6,
            'reserved_stock' => 0,
            'sold_units' => 4,
        ]);
    }

    public function test_bundle_validation_rejects_invalid_shapes(): void
    {
        $standard = $this->createStandardItem('standard', 10);
        $otherTenant = $this->createTenant('other-tenant');
        $foreign = $this->createStandardItem('foreign', 10, $otherTenant);

        foreach ([
            [],
            [['catalog_item_id' => $foreign->id, 'quantity' => 1]],
        ] as $components) {
            try {
                $this->createBundle('invalid-'.count($components), $components);
                $this->fail('The invalid bundle should not have been created.');
            } catch (ValidationException) {
                $this->assertTrue(true);
            }
        }

        $bundle = $this->createBundle('valid-bundle', [
            ['catalog_item_id' => $standard->id, 'quantity' => 1],
        ]);

        $this->expectException(ValidationException::class);
        $this->createBundle('nested-bundle', [
            ['catalog_item_id' => $bundle->id, 'quantity' => 1],
        ]);
    }

    public function test_catalog_api_creates_and_returns_bundle_details(): void
    {
        $component = $this->createStandardItem('api-component', 8);

        $bundleId = $this->postJson("/api/tenants/{$this->tenant->codigo}/catalog-items", [
            'type' => CatalogItemType::Bundle->value,
            'slug' => 'api-bundle',
            'nombre' => 'API Bundle',
            'precio' => 75,
            'components' => [
                ['catalog_item_id' => $component->id, 'quantity' => 2],
            ],
        ])
            ->assertCreated()
            ->assertJsonPath('data.type', CatalogItemType::Bundle->value)
            ->json('data.id');

        $this->getJson("/api/tenants/{$this->tenant->codigo}/catalog-items/{$bundleId}")
            ->assertOk()
            ->assertJsonPath('data.type', CatalogItemType::Bundle->value)
            ->assertJsonPath('data.maximum_addable_quantity', 4)
            ->assertJsonMissingPath('data.stock_tecnico')
            ->assertJsonCount(1, 'data.components')
            ->assertJsonPath('data.components.0.catalog_item_id', $component->id)
            ->assertJsonPath('data.components.0.variant_id', null)
            ->assertJsonPath('data.components.0.quantity', 2);

        $this->postJson("/api/tenants/{$this->tenant->codigo}/catalog-items", [
            'type' => CatalogItemType::Bundle->value,
            'slug' => 'bundle-with-stock',
            'nombre' => 'Invalid Bundle',
            'precio' => 75,
            'real_stock' => 10,
            'components' => [
                ['catalog_item_id' => $component->id, 'quantity' => 1],
            ],
        ])
            ->assertUnprocessable()
            ->assertJsonValidationErrors(['real_stock']);
    }

    public function test_bundle_components_are_preserved_when_the_bundle_is_soft_deleted(): void
    {
        $component = $this->createStandardItem('deletion-component', 5);
        $bundle = $this->createBundle('deletable-bundle', [
            ['catalog_item_id' => $component->id, 'quantity' => 1],
        ]);
        $this->catalogService->delete($bundle);

        $this->assertSoftDeleted('catalog_items', ['id' => $bundle->id]);
        $this->assertDatabaseHas('bundle_components', [
            'bundle_catalog_item_id' => $bundle->id,
        ]);
        $this->assertDatabaseHas('catalog_items', ['id' => $component->id]);
    }

    private function createStandardItem(
        string $slug,
        int $stock,
        ?Tenant $tenant = null,
        InventoryPolicy $inventoryPolicy = InventoryPolicy::Tracked,
    ): CatalogItem {
        $tenant ??= $this->tenant;

        return $this->catalogService->create([
            'tenant_code' => $tenant->codigo,
            'slug' => $slug,
            'nombre' => ucfirst($slug),
            'precio' => '25.00',
            'inventory_policy' => $inventoryPolicy,
            'real_stock' => $stock,
        ]);
    }

    /** @param array<int, array<string, mixed>> $components */
    private function createBundle(
        string $slug,
        array $components,
        string $price = '50.00',
    ): CatalogItem {
        return $this->catalogService->create([
            'tenant_code' => $this->tenant->codigo,
            'type' => CatalogItemType::Bundle->value,
            'slug' => $slug,
            'nombre' => ucfirst($slug),
            'precio' => $price,
            'components' => $components,
        ]);
    }

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

        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' => $header->id,
            'footer_logo_id' => $footer->id,
        ]);
    }
}
