<?php

declare(strict_types=1);

use Builder\Core\Extensions\Resources\Users\UserResource;
use Builder\Core\Foundation\Concerns\HasBuilderAccess;
use Builder\Core\Foundation\Contracts\BuilderAccessible;
use Builder\Core\Foundation\Registry;
use Builder\Core\Notifications\UserInvitedNotification;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Schema;
use Inertia\Testing\AssertableInertia;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Traits\HasRoles;

/**
 * Test user with Spatie HasRoles for UserResource role-assignment tests.
 */
final class UserResourceTestUser extends Authenticatable implements BuilderAccessible
{
    use HasBuilderAccess;
    use HasRoles;
    use Notifiable;

    protected $table = 'users';

    protected $guarded = [];

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

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

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

    Schema::create('password_reset_tokens', function ($table): void {
        $table->string('email')->primary();
        $table->string('token');
        $table->timestamp('created_at')->nullable();
    });

    config(['auth.passwords.builder-invite' => [
        'provider' => 'users',
        'table' => 'password_reset_tokens',
        'expire' => 1440,
        'throttle' => 60,
    ]]);

    Schema::create('roles', function ($table): void {
        $table->id();
        $table->string('name');
        $table->string('guard_name')->default('web');
        $table->timestamps();
        $table->unique(['name', 'guard_name']);
    });

    Schema::create('permissions', function ($table): void {
        $table->id();
        $table->string('name');
        $table->string('guard_name')->default('web');
        $table->timestamps();
        $table->unique(['name', 'guard_name']);
    });

    Schema::create('model_has_roles', function ($table): void {
        $table->foreignId('role_id');
        $table->string('model_type');
        $table->unsignedBigInteger('model_id');
        $table->index(['model_id', 'model_type']);
        $table->primary(['role_id', 'model_id', 'model_type']);
    });

    Schema::create('model_has_permissions', function ($table): void {
        $table->foreignId('permission_id');
        $table->string('model_type');
        $table->unsignedBigInteger('model_id');
        $table->index(['model_id', 'model_type']);
        $table->primary(['permission_id', 'model_id', 'model_type']);
    });

    Schema::create('role_has_permissions', function ($table): void {
        $table->foreignId('permission_id');
        $table->foreignId('role_id');
        $table->primary(['permission_id', 'role_id']);
    });

    Schema::create('revisions', function ($table): void {
        $table->id();
        $table->string('revisable_type');
        $table->unsignedBigInteger('revisable_id');
        $table->unsignedBigInteger('user_id')->nullable();
        $table->json('revision_data');
        $table->timestamps();
    });

    config(['auth.providers.users.model' => UserResourceTestUser::class]);

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

it('UserResource is registered via BuilderServiceProvider', function (): void {
    $registry = app(Registry::class);

    expect($registry->resolveBySlug('users'))->not->toBeNull();
});

it('renders user index page', function (): void {
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->get(route('builder.resource.index', 'users'))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page->component('Resource/Index'));
});

it('renders user create page', function (): void {
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->get(route('builder.resource.create', 'users'))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page->component('Resource/Form'));
});

it('does not require password field on create', function (): void {
    Notification::fake();

    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'users'), [
            'name' => 'Jane Doe',
            'email' => 'jane@example.com',
        ])
        ->assertRedirect(route('builder.resource.index', 'users'));

    expect(UserResourceTestUser::where('email', 'jane@example.com')->exists())->toBeTrue();
});

it('sends UserInvitedNotification on user create', function (): void {
    Notification::fake();

    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'users'), [
            'name' => 'Jane Doe',
            'email' => 'jane@example.com',
        ])
        ->assertRedirect(route('builder.resource.index', 'users'));

    $user = UserResourceTestUser::where('email', 'jane@example.com')->firstOrFail();

    Notification::assertSentTo($user, UserInvitedNotification::class);
});

it('store validates required fields', function (): void {
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'users'), [])
        ->assertSessionHasErrors(['name', 'email']);
});

it('store enforces unique email', function (): void {
    UserResourceTestUser::create(['name' => 'Existing', 'email' => 'taken@example.com', 'password' => 'x']);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'users'), [
            'name' => 'New User',
            'email' => 'taken@example.com',
        ])
        ->assertSessionHasErrors('email');
});

it('renders user edit page', function (): void {
    $target = UserResourceTestUser::create(['name' => 'Target', 'email' => 'target@example.com', 'password' => Hash::make('secret')]);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->get(route('builder.resource.edit', ['resource' => 'users', 'id' => $target->id]))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page->component('Resource/Form'));
});

it('updates a user name', function (): void {
    $target = UserResourceTestUser::create(['name' => 'Old Name', 'email' => 'target@example.com', 'password' => Hash::make('secret')]);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->put(route('builder.resource.update', ['resource' => 'users', 'id' => $target->id]), [
            'name' => 'New Name',
            'email' => 'target@example.com',
        ])
        ->assertRedirect(route('builder.resource.edit', ['resource' => 'users', 'id' => $target->id]));

    $target->refresh();
    expect($target->name)->toBe('New Name');
});

it('deletes a user', function (): void {
    $target = UserResourceTestUser::create(['name' => 'To Delete', 'email' => 'delete@example.com', 'password' => 'x']);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->delete(route('builder.resource.destroy', ['resource' => 'users', 'id' => $target->id]))
        ->assertRedirect(route('builder.resource.index', 'users'));

    expect(UserResourceTestUser::find($target->id))->toBeNull();
});

it('syncs roles on user create', function (): void {
    Notification::fake();

    $role = Role::create(['name' => 'editor', 'guard_name' => 'web']);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'users'), [
            'name' => 'Jane',
            'email' => 'jane@example.com',
            'roles' => $role->id,
        ])
        ->assertRedirect(route('builder.resource.index', 'users'));

    $user = UserResourceTestUser::where('email', 'jane@example.com')->firstOrFail();
    expect($user->roles->pluck('id')->toArray())->toContain($role->id);
});

it('resend-invite record action sends notification for inactive user', function (): void {
    Notification::fake();

    $target = UserResourceTestUser::create(['name' => 'Target', 'email' => 'target@example.com', 'password' => Hash::make('secret'), 'activated_at' => null]);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.record-action', ['resource' => 'users', 'id' => $target->id, 'action' => 'resend-invite']))
        ->assertRedirect();

    Notification::assertSentTo($target, UserInvitedNotification::class);
});

it('send-reset-password record action sends notification for active user', function (): void {
    Notification::fake();

    $target = UserResourceTestUser::create(['name' => 'Target', 'email' => 'target@example.com', 'password' => Hash::make('secret'), 'activated_at' => now()]);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => Hash::make('secret')]);

    $this->actingAs($admin)
        ->post(route('builder.resource.record-action', ['resource' => 'users', 'id' => $target->id, 'action' => 'send-reset-password']))
        ->assertRedirect();

    Notification::assertSentTo($target, UserInvitedNotification::class);
});

it('canDelete prevents deleting the last remaining user', function (): void {
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'only-one@test.com', 'password' => Hash::make('secret')]);

    // Only one user exists, delete should redirect back with error (not remove the user)
    $this->actingAs($admin)
        ->delete(route('builder.resource.destroy', ['resource' => 'users', 'id' => $admin->id]))
        ->assertRedirect();

    expect(UserResourceTestUser::find($admin->id))->not->toBeNull();
});

it('filters users by status=active (whereNotNull activated_at branch)', function (): void {
    UserResourceTestUser::create(['name' => 'Active User', 'email' => 'active2@test.com', 'password' => 'x', 'activated_at' => now()]);
    UserResourceTestUser::create(['name' => 'Invited User', 'email' => 'invited2@test.com', 'password' => 'x', 'activated_at' => null]);

    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@active-filter-test.com', 'password' => 'x', 'activated_at' => now()]);

    $response = $this->actingAs($admin)
        ->get(route('builder.resource.index', 'users').'?status=active');

    $response->assertOk();

    $names = collect($response->viewData('page')['props']['records']['data'])->pluck('name')->all();

    expect($names)->toContain('Active User')
        ->and($names)->not->toContain('Invited User');
});

it('filters users by status=invited (whereNull activated_at branch)', function (): void {
    UserResourceTestUser::create(['name' => 'Active User', 'email' => 'active@test.com', 'password' => 'x', 'activated_at' => now()]);
    UserResourceTestUser::create(['name' => 'Invited User', 'email' => 'invited@test.com', 'password' => 'x', 'activated_at' => null]);

    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'admin@filter-test.com', 'password' => 'x', 'activated_at' => now()]);

    $response = $this->actingAs($admin)
        ->get(route('builder.resource.index', 'users').'?status=invited');

    $response->assertOk();

    $names = collect($response->viewData('page')['props']['records']['data'])->pluck('name')->all();

    expect($names)->toContain('Invited User')
        ->and($names)->not->toContain('Active User');
});

it('bulk delete removes multiple users', function (): void {
    $target1 = UserResourceTestUser::create(['name' => 'Del 1', 'email' => 'del1@test.com', 'password' => 'x']);
    $target2 = UserResourceTestUser::create(['name' => 'Del 2', 'email' => 'del2@test.com', 'password' => 'x']);
    $admin = UserResourceTestUser::create(['name' => 'Admin', 'email' => 'bulk-admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->post(route('builder.resource.bulk', 'users'), [
            'action' => 'delete',
            'ids' => [$target1->id, $target2->id],
        ])
        ->assertRedirect(route('builder.resource.index', 'users'));

    expect(UserResourceTestUser::find($target1->id))->toBeNull()
        ->and(UserResourceTestUser::find($target2->id))->toBeNull()
        ->and(UserResourceTestUser::find($admin->id))->not->toBeNull();
});
