<?php

namespace App\Domains\Ticket\Services;

use App\Domains\Auth\Models\User;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Ticket\Exceptions\TicketGenerationException;
use App\Domains\Ticket\Models\Ticket;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class TicketGeneratorService
{
    public function __construct(private readonly TicketValidityResolver $validityResolver) {}

    /**
     * @return Collection<int, Ticket>
     */
    public function generate(
        CatalogItem $catalogItem,
        User $user,
        int $quantity = 1,
        ?int $sourceVariantId = null,
        ?int $sourcePurchaseId = null,
    ): Collection {
        if ($quantity < 1) {
            throw TicketGenerationException::invalidQuantity();
        }

        return DB::transaction(function () use ($catalogItem, $user, $quantity, $sourceVariantId, $sourcePurchaseId): Collection {
            $targets = $this->resolveTargets(
                $catalogItem,
                $quantity,
                $sourceVariantId,
            );

            return $targets->map(function (array $target) use (
                $sourcePurchaseId,
                $user,
            ): Ticket {
                $item = $target['catalog_item'];
                $variant = $target['variant'];
                $ticket = Ticket::query()->create([
                    'tenant_code' => $item->tenant_code,
                    'ticket' => (string) Str::uuid(),
                    'source_purchase_id' => $sourcePurchaseId,
                    'source_catalog_item_id' => $item->getKey(),
                    'source_variant_id' => $variant?->getKey(),
                    'used_at' => null,
                    'user_id' => $user->getKey(),
                ]);

                return $ticket;
            });
        });
    }

    /** @return Collection<int, array{catalog_item: CatalogItem, variant: Variant|null}> */
    private function resolveTargets(
        CatalogItem $catalogItem,
        int $quantity,
        ?int $sourceVariantId,
    ): Collection {
        if (! $catalogItem->isBundle()) {
            $variant = $this->resolveVariant($catalogItem, $sourceVariantId);
            $this->validateTarget($catalogItem, $variant);

            return $this->targetsForVariant($catalogItem, $variant, $quantity);
        }

        $catalogItem->loadMissing([
            'bundleComponents.catalogItem',
            'bundleComponents.variant.catalogItem',
        ]);

        if ($catalogItem->bundleComponents->isEmpty()) {
            throw TicketGenerationException::emptyBundle($catalogItem);
        }

        return $catalogItem->bundleComponents
            ->flatMap(function ($component) use ($quantity): Collection {
                $componentItem = $component->catalogItem;
                $variant = $component->variant;
                $this->validateTarget($componentItem, $variant);

                return $this->targetsForVariant(
                    $componentItem,
                    $variant,
                    $quantity * $component->quantity,
                );
            })
            ->values();
    }

    /** @return Collection<int, array{catalog_item: CatalogItem, variant: Variant|null}> */
    private function targetsForVariant(
        CatalogItem $catalogItem,
        ?Variant $variant,
        int $quantity,
    ): Collection {
        if ($variant === null) {
            return Collection::times($quantity, fn (): array => [
                'catalog_item' => $catalogItem,
                'variant' => null,
            ]);
        }

        $variant->loadMissing(['eventDates.validityTime', 'eventDate.validityTime']);
        if (! $this->validityResolver->resolveVariant($variant)->isResolvable) {
            throw TicketGenerationException::invalidValidityConfiguration($catalogItem, $variant);
        }

        return Collection::times($quantity, fn (): array => [
            'catalog_item' => $catalogItem,
            'variant' => $variant,
        ]);
    }

    private function resolveVariant(
        CatalogItem $catalogItem,
        ?int $sourceVariantId,
    ): ?Variant {
        if ($sourceVariantId === null) {
            return null;
        }

        $variant = $catalogItem->variants()
            ->whereKey($sourceVariantId)
            ->first();

        if ($variant === null) {
            throw TicketGenerationException::variantNotFound(
                $catalogItem,
                $sourceVariantId,
            );
        }

        $variant->setRelation('catalogItem', $catalogItem);

        return $variant;
    }

    private function validateTarget(
        CatalogItem $catalogItem,
        ?Variant $variant,
    ): void {
        if (! $catalogItem->has_tickets) {
            throw TicketGenerationException::ticketsDisabled($catalogItem);
        }
    }
}
