<?php

declare(strict_types=1);

use Builder\Core\Foundation\Concerns\HasBuilderAccess;
use Builder\Core\Foundation\Contracts\BuilderAccessible;
use Builder\Core\Foundation\Definition\Field;
use Builder\Core\Foundation\Definition\FieldBuilder;
use Builder\Core\Foundation\Definition\Section;
use Builder\Core\Foundation\Registry;
use Builder\Core\Foundation\Resources\Resource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Schema;

final class FieldGatedModel extends Model
{
    protected $table = 'field_gated_items';

    protected $guarded = [];
}

final class FieldGatedResource extends Resource
{
    public static function getSlug(): string
    {
        return 'field-gated';
    }

    public static function getLabel(): string
    {
        return 'Field Gated';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return FieldGatedModel::class;
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Section::make('general')
                ->label('General')
                ->fields([
                    Field::text('title'),
                    Field::text('internal_notes')->canUpdate('update:internal'),
                ]),
            Section::make('restricted')
                ->label('Restricted')
                ->canUpdate('update:restricted')
                ->fields([
                    Field::text('secret_field'),
                ]),
        );
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return [
            'title' => ['nullable', 'string'],
            'internal_notes' => ['nullable', 'string'],
            'secret_field' => ['nullable', 'string'],
        ];
    }
}

final class LimitedUser extends Authenticatable implements BuilderAccessible
{
    use HasBuilderAccess;

    protected $table = 'users';

    protected $guarded = [];

    public function canAccessBuilder(): bool
    {
        return true;
    }

    /** @return list<string> */
    public function builderPermissions(): array
    {
        return ['read:admin'];
    }
}

final class FullUser extends Authenticatable implements BuilderAccessible
{
    use HasBuilderAccess;

    protected $table = 'users';

    protected $guarded = [];

    public function canAccessBuilder(): bool
    {
        return true;
    }

    /** @return list<string> */
    public function builderPermissions(): array
    {
        return ['*'];
    }
}

final class InternalUser extends Authenticatable implements BuilderAccessible
{
    use HasBuilderAccess;

    protected $table = 'users';

    protected $guarded = [];

    public function canAccessBuilder(): bool
    {
        return true;
    }

    /** @return list<string> */
    public function builderPermissions(): array
    {
        return ['read:admin', 'update:internal'];
    }
}

beforeEach(function (): void {
    Schema::create('users', function ($table): void {
        $table->id();
        $table->string('name')->nullable();
        $table->string('email')->nullable();
        $table->string('password')->nullable();
        $table->timestamps();
    });

    Schema::create('field_gated_items', function ($table): void {
        $table->id();
        $table->string('title')->nullable();
        $table->string('internal_notes')->nullable();
        $table->string('secret_field')->nullable();
        $table->timestamps();
    });

    app(Registry::class)->resource(FieldGatedResource::class);
});

it('user with wildcard permissions can update all fields', function (): void {
    $user = FullUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($user)
        ->post(route('builder.resource.store', 'field-gated'), [
            'title' => 'My Title',
            'internal_notes' => 'Secret notes',
            'secret_field' => 'Top secret',
        ])
        ->assertRedirect(route('builder.resource.index', 'field-gated'));

    $record = FieldGatedModel::first();
    expect($record->title)->toBe('My Title')
        ->and($record->internal_notes)->toBe('Secret notes')
        ->and($record->secret_field)->toBe('Top secret');
});

it('user without field update permission cannot update the restricted field', function (): void {
    $user = LimitedUser::create(['name' => 'Limited', 'email' => 'limited@test.com', 'password' => 'x']);

    $this->actingAs($user)
        ->post(route('builder.resource.store', 'field-gated'), [
            'title' => 'My Title',
            'internal_notes' => 'Ignored notes',
            'secret_field' => 'Ignored secret',
        ])
        ->assertRedirect(route('builder.resource.index', 'field-gated'));

    $record = FieldGatedModel::first();
    expect($record->title)->toBe('My Title')
        ->and($record->internal_notes)->toBeNull()
        ->and($record->secret_field)->toBeNull();
});

it('user with internal permission can update the internal field but not the section-gated field', function (): void {
    $user = InternalUser::create(['name' => 'Internal', 'email' => 'internal@test.com', 'password' => 'x']);

    $this->actingAs($user)
        ->post(route('builder.resource.store', 'field-gated'), [
            'title' => 'My Title',
            'internal_notes' => 'Allowed notes',
            'secret_field' => 'Should be ignored',
        ])
        ->assertRedirect(route('builder.resource.index', 'field-gated'));

    $record = FieldGatedModel::first();
    expect($record->title)->toBe('My Title')
        ->and($record->internal_notes)->toBe('Allowed notes')
        ->and($record->secret_field)->toBeNull();
});

it('field stripping applies on update as well', function (): void {
    $record = FieldGatedModel::create([
        'title' => 'Original',
        'internal_notes' => 'Original notes',
        'secret_field' => 'Original secret',
    ]);

    $user = LimitedUser::create(['name' => 'Limited', 'email' => 'limited@test.com', 'password' => 'x']);

    $this->actingAs($user)
        ->put(route('builder.resource.update', ['resource' => 'field-gated', 'id' => $record->id]), [
            'title' => 'Updated Title',
            'internal_notes' => 'Should be ignored',
            'secret_field' => 'Should be ignored',
        ])
        ->assertRedirect(route('builder.resource.edit', ['resource' => 'field-gated', 'id' => $record->id]));

    $record->refresh();
    expect($record->title)->toBe('Updated Title')
        ->and($record->internal_notes)->toBe('Original notes')
        ->and($record->secret_field)->toBe('Original secret');
});
