<?php

namespace Tests\Feature\Ticket;

use App\Domains\Attachable\Models\Attachment;
use App\Domains\Auth\Models\User;
use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Category;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Models\WebsiteType;
use App\Domains\Ticket\Models\Ticket;
use Database\Seeders\AuthorizationSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;

class ScannerTicketControllerTest extends TestCase
{
    use RefreshDatabase;

    private Tenant $tenant;

    private User $scanner;

    private User $ticketOwner;

    private Category $category;

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

        $this->seed(AuthorizationSeeder::class);
        WebsiteType::query()->create(['codigo' => 'onticket', 'nombre' => 'OnTicket']);
        $this->tenant = $this->createTenant('acme');
        $this->scanner = User::factory()->create([
            'rol_codigo' => RoleCode::Scanner->value,
            'tenant_codigo' => $this->tenant->codigo,
        ]);
        $this->ticketOwner = User::factory()->create();
        $this->category = Category::query()->create([
            'tenant_code' => $this->tenant->codigo,
            'nombre' => 'Entradas',
        ]);
        $this->scanner->scanCategories()->attach($this->category);
    }

    public function test_scanner_routes_require_authentication_and_scan_permission(): void
    {
        $this->getJson('/api/v1/scanner/tickets')->assertUnauthorized();

        Sanctum::actingAs(User::factory()->create([
            'rol_codigo' => RoleCode::AdminApp->value,
            'tenant_codigo' => $this->tenant->codigo,
        ]));

        $this->getJson('/api/v1/scanner/tickets')->assertForbidden();
    }

    public function test_scanner_can_list_only_its_scanned_tickets_using_adminapp_format(): void
    {
        $older = $this->createTicket('11111111-1111-4111-8111-111111111111', [
            'used_at' => now()->subMinutes(2),
            'scanner_user_id' => $this->scanner->id,
        ]);
        $newer = $this->createTicket('22222222-2222-4222-8222-222222222222', [
            'used_at' => now()->subMinute(),
            'scanner_user_id' => $this->scanner->id,
        ]);
        $otherScanner = User::factory()->create([
            'rol_codigo' => RoleCode::Scanner->value,
            'tenant_codigo' => $this->tenant->codigo,
        ]);
        $this->createTicket('33333333-3333-4333-8333-333333333333', [
            'used_at' => now(),
            'scanner_user_id' => $otherScanner->id,
        ]);

        Sanctum::actingAs($this->scanner);

        $this->getJson('/api/v1/scanner/tickets')
            ->assertOk()
            ->assertJsonCount(2, 'data')
            ->assertJsonPath('data.0.id', $newer->id)
            ->assertJsonPath('data.0.ticket', $newer->ticket)
            ->assertJsonPath('data.0.status', Ticket::STATUS_USED)
            ->assertJsonPath('data.1.id', $older->id)
            ->assertJsonPath('meta.current_page', 1)
            ->assertJsonPath('meta.total', 2);
    }

    public function test_scanner_ticket_history_supports_id_search_and_pagination(): void
    {
        $matching = $this->createTicket('aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', [
            'used_at' => now(),
            'scanner_user_id' => $this->scanner->id,
        ]);
        $this->createTicket('bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', [
            'used_at' => now()->subMinute(),
            'scanner_user_id' => $this->scanner->id,
        ]);

        Sanctum::actingAs($this->scanner);

        $this->getJson('/api/v1/scanner/tickets?q=aaaaaaaa&per_page=1')
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.ticket', $matching->ticket)
            ->assertJsonPath('meta.current_page', 1)
            ->assertJsonPath('meta.per_page', 1)
            ->assertJsonPath('meta.total', 1);
    }

    public function test_scanner_ticket_history_can_be_searched_by_database_id(): void
    {
        $matching = $this->createTicket('cccccccc-cccc-4ccc-8ccc-cccccccccccc', [
            'used_at' => now(),
            'scanner_user_id' => $this->scanner->id,
        ]);
        $this->createTicket('dddddddd-dddd-4ddd-8ddd-dddddddddddd', [
            'used_at' => now()->subMinute(),
            'scanner_user_id' => $this->scanner->id,
        ]);

        Sanctum::actingAs($this->scanner);

        $this->getJson("/api/v1/scanner/tickets?q={$matching->id}")
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.id', $matching->id);
    }

    public function test_scanner_ticket_history_can_be_searched_by_used_date(): void
    {
        $matching = $this->createTicket('eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', [
            'used_at' => '2026-08-11 14:30:00',
            'scanner_user_id' => $this->scanner->id,
        ]);
        $this->createTicket('ffffffff-ffff-4fff-8fff-ffffffffffff', [
            'used_at' => '2026-08-10 14:30:00',
            'scanner_user_id' => $this->scanner->id,
        ]);

        Sanctum::actingAs($this->scanner);

        $this->getJson('/api/v1/scanner/tickets?q=11%2F08%2F26')
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.id', $matching->id);
    }

    public function test_scanner_can_read_an_authorized_ticket_detail_by_uuid(): void
    {
        $ticket = $this->createTicket('44444444-4444-4444-8444-444444444444');
        Sanctum::actingAs($this->scanner);

        $this->getJson("/api/v1/scanner/tickets/{$ticket->ticket}")
            ->assertOk()
            ->assertJsonPath('data.id', $ticket->id)
            ->assertJsonPath('data.ticket', $ticket->ticket)
            ->assertJsonPath('data.name', $ticket->name)
            ->assertJsonPath('data.client', $this->ticketOwner->nombre_apellido)
            ->assertJsonPath('data.category', $this->category->nombre)
            ->assertJsonPath('data.is_valid', true)
            ->assertJsonPath('data.is_used', false);
    }

    public function test_scanner_cannot_read_a_ticket_from_an_unassigned_category_or_tenant(): void
    {
        $otherCategory = Category::query()->create([
            'tenant_code' => $this->tenant->codigo,
            'nombre' => 'Comidas',
        ]);
        $unassignedTicket = $this->createTicket(
            '55555555-5555-4555-8555-555555555555',
            [],
            $otherCategory,
        );
        $otherTenant = $this->createTenant('other');
        $foreignCategory = Category::query()->create([
            'tenant_code' => $otherTenant->codigo,
            'nombre' => 'Externas',
        ]);
        $foreignTicket = $this->createTicket(
            '66666666-6666-4666-8666-666666666666',
            [],
            $foreignCategory,
            $otherTenant,
        );
        Sanctum::actingAs($this->scanner);

        $this->getJson("/api/v1/scanner/tickets/{$unassignedTicket->ticket}")
            ->assertNotFound();
        $this->getJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}")
            ->assertNotFound();
    }

    public function test_scanner_can_scan_an_authorized_ticket_by_uuid(): void
    {
        $ticket = $this->createTicket('77777777-7777-4777-8777-777777777777');
        Sanctum::actingAs($this->scanner);

        $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
            ->assertOk()
            ->assertJsonPath('data.ticket', $ticket->ticket)
            ->assertJsonPath('data.scanner_user_id', $this->scanner->id)
            ->assertJsonPath('data.is_valid', false)
            ->assertJsonPath('data.is_used', true);

        $this->assertDatabaseHas('tickets', [
            'id' => $ticket->id,
            'scanner_user_id' => $this->scanner->id,
        ]);
        $this->assertNotNull($ticket->fresh()->used_at);
    }

    public function test_ticket_cannot_be_scanned_twice(): void
    {
        $ticket = $this->createTicket('88888888-8888-4888-8888-888888888888');
        Sanctum::actingAs($this->scanner);
        $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")->assertOk();

        $otherScanner = User::factory()->create([
            'rol_codigo' => RoleCode::Scanner->value,
            'tenant_codigo' => $this->tenant->codigo,
        ]);
        $otherScanner->scanCategories()->attach($this->category);
        Sanctum::actingAs($otherScanner);

        $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
            ->assertUnprocessable()
            ->assertJsonValidationErrors('ticket');
        $this->assertSame($this->scanner->id, $ticket->fresh()->scanner_user_id);
    }

    public function test_scanner_cannot_scan_a_ticket_from_an_unassigned_category(): void
    {
        $otherCategory = Category::query()->create([
            'tenant_code' => $this->tenant->codigo,
            'nombre' => 'Comidas',
        ]);
        $ticket = $this->createTicket(
            '99999999-9999-4999-8999-999999999999',
            [],
            $otherCategory,
        );
        Sanctum::actingAs($this->scanner);

        $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
            ->assertUnprocessable()
            ->assertJsonValidationErrors('ticket');
        $this->assertNull($ticket->fresh()->used_at);
    }

    public function test_scanner_can_read_and_scan_any_category_when_tenant_disables_validation(): void
    {
        $this->tenant->update(['scanner_category_validation_enabled' => false]);
        $this->scanner->scanCategories()->detach();
        $otherCategory = Category::query()->create([
            'tenant_code' => $this->tenant->codigo,
            'nombre' => 'Comidas',
        ]);
        $ticket = $this->createTicket(
            'aaaaaaaa-1111-4111-8111-aaaaaaaaaaaa',
            [],
            $otherCategory,
        );
        Sanctum::actingAs($this->scanner);

        $this->getJson("/api/v1/scanner/tickets/{$ticket->ticket}")
            ->assertOk()
            ->assertJsonPath('data.id', $ticket->id);

        $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
            ->assertOk()
            ->assertJsonPath('data.scanner_user_id', $this->scanner->id);
    }

    public function test_disabled_category_validation_does_not_allow_scanning_another_tenant(): void
    {
        $this->tenant->update(['scanner_category_validation_enabled' => false]);
        $otherTenant = $this->createTenant('other');
        $foreignCategory = Category::query()->create([
            'tenant_code' => $otherTenant->codigo,
            'nombre' => 'Externas',
        ]);
        $foreignTicket = $this->createTicket(
            'bbbbbbbb-1111-4111-8111-bbbbbbbbbbbb',
            [],
            $foreignCategory,
            $otherTenant,
        );
        Sanctum::actingAs($this->scanner);

        $this->getJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}")
            ->assertNotFound();
        $this->postJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}/scan")
            ->assertNotFound();
    }

    /** @param array<string, mixed> $attributes */
    private function createTicket(
        string $uuid,
        array $attributes = [],
        ?Category $category = null,
        ?Tenant $tenant = null,
    ): Ticket {
        $tenant ??= $this->tenant;
        $category ??= $this->category;
        $catalogItem = CatalogItem::query()->create([
            'tenant_code' => $tenant->codigo,
            'category_id' => $category->id,
            'slug' => "ticket-{$uuid}",
            'nombre' => 'Entrada general',
            'descripcion' => 'Acceso general',
            'precio' => 100,
            'has_tickets' => true,
        ]);

        return Ticket::query()->create(array_merge([
            'tenant_code' => $tenant->codigo,
            'ticket' => $uuid,
            'source_catalog_item_id' => $catalogItem->id,
            'user_id' => $this->ticketOwner->id,
        ], $attributes));
    }

    private function createTenant(string $code): Tenant
    {
        $logo = Attachment::query()->create([
            'key' => (string) Str::uuid(),
            'path' => "tests/{$code}-logo.png",
            'filename' => "{$code}-logo.png",
            'type' => 'image',
            'mime_type' => 'image/png',
            'extension' => 'png',
            'size' => 1,
        ]);

        return Tenant::query()->create([
            'codigo' => $code,
            'nombre' => ucfirst($code),
            'dominio' => "{$code}.test",
            'primary_color' => '#ff7006',
            'secondary_color' => '#777777',
            'danger_color' => '#e04a4a',
            'success_color' => '#81bc73',
            'header_bg_color' => '#313131',
            'footer_bg_color' => '#313131',
            'header_logo_id' => $logo->id,
            'footer_logo_id' => $logo->id,
            'website_type_code' => 'onticket',
        ]);
    }
}
