<?php

namespace Tests\Unit\Ticket;

use App\Domains\Auth\Models\User;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Models\Ticket;
use App\Domains\Ticket\Models\ValidityTime;
use App\Domains\Ticket\Services\ResolvedTicketValidity;
use App\Domains\Ticket\Services\ResolvedValidityGroup;
use App\Domains\Ticket\Services\TicketValidityResolver;
use Illuminate\Support\Carbon;
use Tests\TestCase;

class TicketTest extends TestCase
{
    protected function tearDown(): void
    {
        Carbon::setTestNow();

        parent::tearDown();
    }

    public function test_it_maps_its_fields_and_relations(): void
    {
        $ticket = new Ticket;
        $ticket->setRawAttributes([
            'source_catalog_item_id' => '20',
            'source_variant_id' => '30',
            'used_at' => null,
            'scanner_user_id' => '15',
            'user_id' => '10',
        ]);

        $this->assertSame('tickets', $ticket->getTable());
        $this->assertFalse($ticket->usesTimestamps());
        $this->assertSame(20, $ticket->source_catalog_item_id);
        $this->assertSame(30, $ticket->source_variant_id);
        $this->assertNull($ticket->used_at);
        $this->assertSame(15, $ticket->scanner_user_id);
        $this->assertSame(10, $ticket->user_id);
        $this->assertInstanceOf(Tenant::class, $ticket->tenant()->getRelated());
        $this->assertInstanceOf(User::class, $ticket->user()->getRelated());
        $this->assertInstanceOf(User::class, $ticket->scannerUser()->getRelated());
        $this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated());
        $this->assertInstanceOf(Variant::class, $ticket->sourceVariant()->getRelated());
    }

    public function test_unused_ticket_without_validity_time_is_valid(): void
    {
        $ticket = new Ticket;

        $this->assertTrue($ticket->isValid());
        $this->assertSame(Ticket::STATUS_ACTIVE, $ticket->status);
    }

    public function test_fixed_window_controls_ticket_validity(): void
    {
        Carbon::setTestNow('2026-07-21 10:00:00');
        $ticket = $this->ticketWithValidityTime(new ValidityTime([
            'type' => ValidityTimeType::FixedWindow,
            'fixed_starts_at' => now()->subHour(),
            'fixed_expires_at' => now()->addHour(),
        ]));

        $this->assertTrue($ticket->isValid());
        $this->assertFalse($ticket->is_expired);
    }

    public function test_time_window_is_resolved_for_current_date(): void
    {
        Carbon::setTestNow('2026-07-21 12:00:00');
        $ticket = $this->ticketWithValidityGroups([[new ValidityTime([
            'type' => ValidityTimeType::TimeWindow,
            'start_time' => '10:00:00',
            'end_time' => '14:00:00',
        ])]]);

        $this->assertSame('2026-07-21 10:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s'));
        $this->assertSame('2026-07-21 14:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s'));
        $this->assertTrue($ticket->isValid());
    }

    public function test_ticket_is_invalid_when_validity_time_expires(): void
    {
        Carbon::setTestNow('2026-07-21 10:00:00');
        $ticket = $this->ticketWithValidityTime(new ValidityTime([
            'type' => ValidityTimeType::FixedWindow,
            'fixed_expires_at' => now(),
        ]));

        $this->assertFalse($ticket->isValid());
        $this->assertTrue($ticket->is_expired);
        $this->assertSame(Ticket::STATUS_EXPIRED, $ticket->status);
    }

    public function test_ticket_is_valid_when_any_validity_time_is_active(): void
    {
        Carbon::setTestNow('2026-07-21 10:00:00');
        $ticket = $this->ticketWithValidityGroups([
            [new ValidityTime([
                'type' => ValidityTimeType::FixedWindow,
                'fixed_starts_at' => now()->subDays(2),
                'fixed_expires_at' => now()->subDay(),
            ])],
            [new ValidityTime([
                'type' => ValidityTimeType::FixedWindow,
                'fixed_starts_at' => now()->subHour(),
                'fixed_expires_at' => now()->addHour(),
            ])],
        ]);

        $this->assertTrue($ticket->isValid());
        $this->assertFalse($ticket->is_expired);
        $this->assertSame('2026-07-19 10:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s'));
        $this->assertSame('2026-07-21 11:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s'));
    }

    public function test_ticket_is_invalid_but_not_expired_between_validity_times(): void
    {
        Carbon::setTestNow('2026-07-21 10:00:00');
        $ticket = $this->ticketWithValidityGroups([
            [new ValidityTime([
                'type' => ValidityTimeType::FixedWindow,
                'fixed_starts_at' => now()->subDays(2),
                'fixed_expires_at' => now()->subDay(),
            ])],
            [new ValidityTime([
                'type' => ValidityTimeType::FixedWindow,
                'fixed_starts_at' => now()->addDay(),
                'fixed_expires_at' => now()->addDays(2),
            ])],
        ]);

        $this->assertFalse($ticket->isValid());
        $this->assertFalse($ticket->is_expired);
        $this->assertSame(Ticket::STATUS_ACTIVE, $ticket->status);
    }

    public function test_used_ticket_is_invalid_and_not_reported_as_expired(): void
    {
        $ticket = $this->ticketWithValidityTime(new ValidityTime([
            'type' => ValidityTimeType::FixedWindow,
            'fixed_expires_at' => now()->subHour(),
        ]));
        $ticket->used_at = now()->subMinute();

        $this->assertFalse($ticket->is_valid);
        $this->assertFalse($ticket->is_expired);
        $this->assertTrue($ticket->is_used);
        $this->assertSame(Ticket::STATUS_USED, $ticket->status);
    }

    public function test_all_validity_times_in_the_same_group_must_be_active(): void
    {
        Carbon::setTestNow('2026-08-20 13:00:00');
        $ticket = $this->ticketWithValidityGroups([[
            new ValidityTime([
                'type' => ValidityTimeType::FixedWindow,
                'fixed_starts_at' => '2026-08-20 09:00:00',
                'fixed_expires_at' => '2026-08-20 18:00:00',
            ]),
            new ValidityTime([
                'type' => ValidityTimeType::TimeWindow,
                'start_time' => '12:00:00',
                'end_time' => '15:00:00',
            ]),
        ]]);

        $this->assertTrue($ticket->isValid());
        $this->assertSame('2026-08-20 12:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s'));
        $this->assertSame('2026-08-20 15:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s'));

        Carbon::setTestNow('2026-08-20 16:00:00');

        $this->assertFalse($ticket->isValid());
        $this->assertTrue($ticket->is_expired);
    }

    private function ticketWithValidityTime(ValidityTime $validityTime): Ticket
    {
        return $this->ticketWithValidityGroups([[$validityTime]]);
    }

    /** @param array<int, array<int, ValidityTime>> $validityGroups */
    private function ticketWithValidityGroups(array $validityGroups): Ticket
    {
        $ticket = new Ticket;
        $resolved = new ResolvedTicketValidity(
            collect($validityGroups)->map(
                fn (array $validityTimes): ResolvedValidityGroup => new ResolvedValidityGroup(collect($validityTimes))
            )
        );
        $this->app->instance(
            TicketValidityResolver::class,
            new class($resolved) extends TicketValidityResolver
            {
                public function __construct(private readonly ResolvedTicketValidity $resolved) {}

                public function resolveTicket(Ticket $ticket): ResolvedTicketValidity
                {
                    return $this->resolved;
                }
            }
        );

        return $ticket;
    }
}
