<?php

namespace Tests\Unit\Ticket;

use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\AttributeOption;
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\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Models\ValidityTime;
use App\Domains\Ticket\Services\TicketValidityResolver;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Carbon;
use Tests\TestCase;

class TicketValidityResolverTest extends TestCase
{
    private TicketValidityResolver $resolver;

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

        $this->resolver = new TicketValidityResolver;
    }

    public function test_event_dates_and_multi_select_options_expand_to_or_groups(): void
    {
        $dates = new EloquentCollection([
            $this->eventDate('2026-08-20'),
            $this->eventDate('2026-08-21'),
        ]);
        [$itemAttribute, $definitions] = $this->attributeDimension(
            allowMultiSelect: true,
            options: [
                'Desayuno' => $this->timeWindow('07:00:00', '12:00:00'),
                'Cena' => $this->timeWindow('20:00:00', '24:00:00'),
            ],
        );
        $variant = $this->variant($dates, $definitions);

        $validity = $this->resolver->resolveVariant($variant);

        $this->assertTrue($validity->isResolvable);
        $this->assertFalse($validity->isUnrestricted);
        $this->assertCount(4, $validity->groups);
        $this->assertTrue($validity->groups->every(
            fn ($group): bool => $group->validityTimes->count() === 2
        ));
        $this->assertSame($itemAttribute, $definitions->first()->itemAttribute);
    }

    public function test_different_temporal_attributes_are_anded_in_the_same_group(): void
    {
        [, $scheduleDefinitions] = $this->attributeDimension(
            allowMultiSelect: false,
            options: ['Almuerzo' => $this->timeWindow('12:00:00', '15:00:00')],
        );
        [, $admissionDefinitions] = $this->attributeDimension(
            allowMultiSelect: false,
            options: ['Ingreso' => $this->timeWindow('11:00:00', '14:00:00')],
        );
        $variant = $this->variant(
            new EloquentCollection([$this->eventDate('2026-08-20')]),
            new EloquentCollection([
                ...$scheduleDefinitions,
                ...$admissionDefinitions,
            ]),
        );

        $validity = $this->resolver->resolveVariant($variant);

        $this->assertCount(1, $validity->groups);
        $this->assertCount(3, $validity->groups->sole()->validityTimes);
        $this->assertSame(
            '2026-08-20 12:00:00',
            $validity->effectiveStartsAt(Carbon::parse('2026-08-20 13:00:00'))->format('Y-m-d H:i:s'),
        );
        $this->assertSame(
            '2026-08-20 14:00:00',
            $validity->effectiveExpiresAt(Carbon::parse('2026-08-20 13:00:00'))->format('Y-m-d H:i:s'),
        );
        $this->assertTrue($validity->isValid(Carbon::parse('2026-08-20 13:00:00')));
        $this->assertFalse($validity->isValid(Carbon::parse('2026-08-20 14:00:00')));
    }

    public function test_variant_without_temporal_dimensions_is_unrestricted(): void
    {
        $validity = $this->resolver->resolveVariant(
            $this->variant(new EloquentCollection, new EloquentCollection)
        );

        $this->assertTrue($validity->isResolvable);
        $this->assertTrue($validity->isUnrestricted);
        $this->assertTrue($validity->isValid());
        $this->assertFalse($validity->isExpired());
    }

    public function test_non_option_attributes_do_not_affect_validity(): void
    {
        $attribute = new Attribute(['type' => FieldType::String]);
        $attribute->setRelation('options', new EloquentCollection);
        $itemAttribute = new ItemAttribute(['allow_multi_select' => false]);
        $itemAttribute->setRelation('attribute', $attribute);
        $definition = new VariantDefinition(['value' => 'Comedor principal']);
        $definition->setAttribute('item_attribute_id', 123);
        $definition->setRelation('itemAttribute', $itemAttribute);

        $validity = $this->resolver->resolveVariant(
            $this->variant(new EloquentCollection, new EloquentCollection([$definition]))
        );

        $this->assertTrue($validity->isResolvable);
        $this->assertTrue($validity->isUnrestricted);
    }

    public function test_multiple_values_for_a_single_select_attribute_are_unresolvable(): void
    {
        [, $definitions] = $this->attributeDimension(
            allowMultiSelect: false,
            options: [
                'Desayuno' => $this->timeWindow('07:00:00', '12:00:00'),
                'Cena' => $this->timeWindow('20:00:00', '24:00:00'),
            ],
        );

        $validity = $this->resolver->resolveVariant(
            $this->variant(new EloquentCollection, $definitions)
        );

        $this->assertFalse($validity->isResolvable);
        $this->assertFalse($validity->isValid());
    }

    private function eventDate(string $date): EventDate
    {
        $validityTime = new ValidityTime([
            'type' => ValidityTimeType::FixedWindow,
            'fixed_starts_at' => "{$date} 00:00:00",
            'fixed_expires_at' => "{$date} 23:59:59",
        ]);
        $eventDate = new EventDate([
            'date' => $date,
            'time_start' => '00:00:00',
            'time_end' => '23:59:59',
        ]);
        $eventDate->setRelation('validityTime', $validityTime);

        return $eventDate;
    }

    private function timeWindow(string $start, string $end): ValidityTime
    {
        return new ValidityTime([
            'type' => ValidityTimeType::TimeWindow,
            'start_time' => $start,
            'end_time' => $end,
        ]);
    }

    /**
     * @param  array<string, ValidityTime|null>  $options
     * @return array{ItemAttribute, EloquentCollection<int, VariantDefinition>}
     */
    private function attributeDimension(bool $allowMultiSelect, array $options): array
    {
        $attribute = new Attribute([
            'type' => FieldType::Select,
        ]);
        $attributeOptions = collect($options)->map(
            function (?ValidityTime $validityTime, string $value): AttributeOption {
                $option = new AttributeOption(['value' => $value, 'label' => $value]);
                $option->setRelation('validityTime', $validityTime);

                return $option;
            }
        );
        $attribute->setRelation('options', new EloquentCollection($attributeOptions));

        $itemAttribute = new ItemAttribute(['allow_multi_select' => $allowMultiSelect]);
        $itemAttribute->setRelation('attribute', $attribute);
        $definitions = $attributeOptions->map(function (AttributeOption $option) use ($itemAttribute): VariantDefinition {
            $definition = new VariantDefinition(['value' => $option->value]);
            $definition->setAttribute('item_attribute_id', spl_object_id($itemAttribute));
            $definition->setRelation('itemAttribute', $itemAttribute);

            return $definition;
        });

        return [$itemAttribute, new EloquentCollection($definitions)];
    }

    /**
     * @param  EloquentCollection<int, EventDate>  $eventDates
     * @param  EloquentCollection<int, VariantDefinition>  $definitions
     */
    private function variant(EloquentCollection $eventDates, EloquentCollection $definitions): Variant
    {
        $variant = new Variant;
        $variant->setRelation('eventDates', $eventDates);
        $variant->setRelation('eventDate', null);
        $variant->setRelation('definitions', $definitions);

        return $variant;
    }
}
