<?php

use App\Enums\Permission as PermissionEnum;
use App\Models\User;
use App\Models\UserProfile;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->seed(RolePermissionSeeder::class);
    $disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
    Storage::fake($disk);
    Storage::fake('public');
});

function createUserWithProfile(): User
{
    $user = User::factory()->create();
    $user->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
    $user->forgetCachedPermissions();
    UserProfile::factory()->create([
        'user_id' => $user->id,
        'full_name' => fake()->name(),
        'phone_number' => fake()->numerify('08##########'),
        'gender' => fake()->randomElement(['male', 'female']),
    ]);

    return $user;
}

function createUserWithoutProfile(): User
{
    $user = User::factory()->create();
    $user->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
    $user->forgetCachedPermissions();

    return $user;
}

function validProfilePayload(): array
{
    return [
        'email' => 'updated@example.com',
        'username' => 'updated_user',
        'full_name' => 'Nama Updated',
        'phone_number' => '081234567890',
        'gender' => 'male',
        'birth_date' => '1995-01-15',
        'address' => 'Jl. Merdeka No. 1',
    ];
}

// ─── Profile Edit ──────────────────────────────────────────

describe('Profile Edit', function () {
    test('authenticated user can view profile edit form', function () {
        $user = createUserWithProfile();

        $this->actingAs($user)
            ->get(route('admin.account.profile'))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $this->get(route('admin.account.profile'))
            ->assertRedirect(route('login'));
    });
});

// ─── Profile Update ────────────────────────────────────────

describe('Profile Update', function () {
    test('authenticated user can update profile', function () {
        $user = createUserWithProfile();

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), validProfilePayload())
            ->assertRedirect(route('admin.account.profile'));

        $user->refresh();
        expect($user->email)->toBe('updated@example.com');
        expect($user->username)->toBe('updated_user');
        expect($user->profile->full_name)->toBe('Nama Updated');
        expect($user->profile->phone_number)->toBe('081234567890');
        expect($user->profile->gender->value)->toBe('male');
        expect($user->profile->birth_date->format('Y-m-d'))->toBe('1995-01-15');
        expect($user->profile->address)->toBe('Jl. Merdeka No. 1');
    });

    test('guest cannot update profile', function () {
        $this->put(route('admin.account.profile.update'), validProfilePayload())
            ->assertRedirect(route('login'));
    });

    test('profile is created when user has no profile', function () {
        $user = createUserWithoutProfile();

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), validProfilePayload())
            ->assertRedirect(route('admin.account.profile'));

        $user->refresh();
        expect($user->profile)->not->toBeNull();
        expect($user->profile->full_name)->toBe('Nama Updated');
    });

    test('required fields are validated', function () {
        $user = createUserWithProfile();

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), [])
            ->assertSessionHasErrors(['email', 'username', 'full_name']);
    });

    test('email must be unique on update (ignoring self)', function () {
        $user = createUserWithProfile();

        $payload = validProfilePayload();
        $payload['email'] = $user->email;
        $payload['username'] = $user->username;

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload)
            ->assertSessionHasNoErrors('email');
    });

    test('email must be unique among other users', function () {
        $user = createUserWithProfile();
        $other = User::factory()->create(['email' => 'taken@example.com']);

        $payload = validProfilePayload();
        $payload['email'] = 'taken@example.com';

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload)
            ->assertSessionHasErrors('email');
    });

    test('username must be unique on update (ignoring self)', function () {
        $user = createUserWithProfile();

        $payload = validProfilePayload();
        $payload['email'] = $user->email;
        $payload['username'] = $user->username;

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload)
            ->assertSessionHasNoErrors('username');
    });

    test('nullable fields can be null', function () {
        $user = createUserWithProfile();

        $payload = validProfilePayload();
        $payload['phone_number'] = null;
        $payload['gender'] = null;
        $payload['birth_date'] = null;
        $payload['address'] = null;

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload)
            ->assertRedirect(route('admin.account.profile'));

        $user->refresh();
        expect($user->profile->phone_number)->toBeNull();
        expect($user->profile->gender)->toBeNull();
        expect($user->profile->birth_date)->toBeNull();
        expect($user->profile->address)->toBeNull();
    });

    test('updating profile with s3 key links the photo', function () {
        $user = createUserWithProfile();

        $disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
        $imageContent = UploadedFile::fake()->image('avatar.jpg')->get();
        Storage::disk($disk)->put('fake-s3-key.jpg', $imageContent);

        $payload = validProfilePayload();
        $payload['profile_s3_key'] = 'fake-s3-key.jpg';

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload);

        $user->refresh();
        expect($user->profile->getMedia('profile_photo')->count())->toBe(1);
    });

    test('updating profile can upload new photo and delete existing photo', function () {
        $user = createUserWithProfile();

        $imageContent = UploadedFile::fake()->image('initial.jpg')->get();
        Storage::disk('public')->put('initial.jpg', $imageContent);
        $user->profile->addMediaFromDisk('initial.jpg', 'public')->toMediaCollection('profile_photo');
        expect($user->profile->fresh()->getMedia('profile_photo')->count())->toBe(1);
        $mediaId = $user->profile->fresh()->getFirstMedia('profile_photo')->id;

        $disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
        $imageContent2 = UploadedFile::fake()->image('updated.jpg')->get();
        Storage::disk($disk)->put('updated.jpg', $imageContent2);

        $payload = validProfilePayload();
        $payload['profile_s3_key'] = 'updated.jpg';
        $payload['remove_profile_photo_ids'] = [$mediaId];

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload);

        $freshProfile = $user->fresh()->profile;
        expect($freshProfile->getMedia('profile_photo')->count())->toBe(1);
        expect($freshProfile->getFirstMedia('profile_photo')->id)->not->toBe($mediaId);
    });

    test('removing profile photo without uploading new one results in no photo', function () {
        $user = createUserWithProfile();

        $imageContent = UploadedFile::fake()->image('initial.jpg')->get();
        Storage::disk('public')->put('initial.jpg', $imageContent);
        $user->profile->addMediaFromDisk('initial.jpg', 'public')->toMediaCollection('profile_photo');
        $mediaId = $user->profile->fresh()->getFirstMedia('profile_photo')->id;

        $payload = validProfilePayload();
        $payload['remove_profile_photo_ids'] = [$mediaId];

        $this->actingAs($user)
            ->put(route('admin.account.profile.update'), $payload);

        $user->refresh();
        expect($user->profile->getMedia('profile_photo')->count())->toBe(0);
    });
});

// ─── Password Edit ─────────────────────────────────────────

describe('Password Edit', function () {
    test('authenticated user can view password edit form', function () {
        $user = createUserWithProfile();

        $this->actingAs($user)
            ->get(route('admin.account.password'))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $this->get(route('admin.account.password'))
            ->assertRedirect(route('login'));
    });
});

// ─── Password Update ───────────────────────────────────────

describe('Password Update', function () {
    test('authenticated user can update password', function () {
        $user = User::factory()->create(['password' => Hash::make('old-password')]);
        $user->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
        $user->forgetCachedPermissions();

        $this->actingAs($user)
            ->put(route('admin.account.password.update'), [
                'current_password' => 'old-password',
                'password' => 'new-secure-password',
                'password_confirmation' => 'new-secure-password',
            ])
            ->assertRedirect(route('admin.account.password'));

        $this->assertTrue(Hash::check('new-secure-password', $user->fresh()->password));
    });

    test('guest cannot update password', function () {
        $this->put(route('admin.account.password.update'), [
            'current_password' => 'old-password',
            'password' => 'new-secure-password',
            'password_confirmation' => 'new-secure-password',
        ])->assertRedirect(route('login'));
    });

    test('current password must be correct', function () {
        $user = User::factory()->create(['password' => Hash::make('old-password')]);
        $user->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
        $user->forgetCachedPermissions();

        $this->actingAs($user)
            ->put(route('admin.account.password.update'), [
                'current_password' => 'wrong-password',
                'password' => 'new-secure-password',
                'password_confirmation' => 'new-secure-password',
            ])
            ->assertSessionHasErrors('current_password');
    });

    test('password confirmation must match', function () {
        $user = User::factory()->create(['password' => Hash::make('old-password')]);
        $user->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
        $user->forgetCachedPermissions();

        $this->actingAs($user)
            ->put(route('admin.account.password.update'), [
                'current_password' => 'old-password',
                'password' => 'new-secure-password',
                'password_confirmation' => 'different-password',
            ])
            ->assertSessionHasErrors('password');
    });

    test('required fields are validated', function () {
        $user = createUserWithoutProfile();

        $this->actingAs($user)
            ->put(route('admin.account.password.update'), [])
            ->assertSessionHasErrors(['current_password', 'password']);
    });
});
