<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Tests\TestCase;

use Illuminate\Support\Arr;

use App\Models\User;
use App\Models\Role;
use App\Models\Livestream;
use App\Models\Inquiry;
use App\Models\Chat;
use App\Models\Page;
use App\Models\StaffProfile;

use App\Events\UserBanned;
use App\Mail\EmailVerification;
use App\Mail\ResetPassword;

class UserTest extends TestCase
{
    use WithFaker;

    public function test_the_users_index_can_be_loaded()
    {
        $this->get(route('users.index'))
            ->assertStatus(302);

        $this->signIn(User::factory()->create());

        $this->withoutExceptionHandling();
        $this->get(route('users.index'))
            ->assertRedirect('/');

        $this->signInAdmin();

        $this->get(route('users.index'))
            ->assertSuccessful();
    }

    public function test_all_users_can_be_loaded()
    {
        $this->json('POST', route('users-index.paginate'))
            ->assertStatus(401);

        $this->signIn(User::factory()->create());

        $this->json('POST', route('users-index.paginate'))
            ->assertStatus(403);

        $this->signInAdmin();

        $user = User::factory()->create();
        $role = Role::all()->random();
        $user->addRole($role);

        $this->json('POST', route('users-index.paginate', ['paginate_count' => User::count()]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $user->name,
                'name' => $role->name,
             ]);
    }

    public function test_a_user_can_be_searched_for()
    {
        $this->signInAdmin();

        $user = User::factory()->create();
        $this->assertNotNull($user->last_name);

        $this->json('POST', route('users-index.search', ['terms' => $user->last_name]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $user->name,
             ]);

        $role = Role::factory()->create();
        $user->addRole($role);

        $this->json('POST', route('users-index.search', ['terms' => $role->name]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $user->name,
             ]);
    }

    public function test_a_user_can_be_save_with_their_roles()
    {
        $user = User::factory()->create();
        $role = Role::all()->random();
        $input = User::factory()->raw();
        $input['roles'] = [$role];

        $this->assertFalse($user->hasRole($role));

        $this->json('POST', route('users.update', ['id' => $user->id]), $input)
            ->assertStatus(401);

        $this->signIn(User::factory()->create());

        $this->json('POST', route('users.update', ['id' => $user->id]), $input)
            ->assertStatus(403);

        $this->signInAdmin();

        $this->json('POST', route('users.update', ['id' => $user->id]), [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'name',
                'email',
             ]);

        $this->withoutExceptionHandling();
        $this->json('POST', route('users.update', ['id' => $user->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'name').' Saved',
             ]);

        $user->refresh();

        $this->assertEquals(Arr::get($input, 'name'), $user->name);
        $this->assertEquals(Arr::get($input, 'email'), $user->email);
        $this->assertTrue($user->hasRole($role));
    }

    public function test_a_users_role_is_removed_if_it_is_not_included_in_the_save_input()
    {
        $user = User::factory()->create();
        $role = Role::all()->random();

        $user->addRole($role);
        $user->refresh();

        $this->assertTrue($user->hasRole($role));

        $input = User::factory()->raw();
        $input['roles'] = [];

        $this->signInAdmin();

        $this->withoutExceptionHandling();

        $this->json('POST', route('users.update', ['id' => $user->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'name').' Saved',
             ]);

        $user->refresh();
        $this->assertFalse($user->hasRole($role));
    }

    public function test_users_can_be_searched()
    {
        $user = User::factory()->create();
        $input = [
            'autocomplete' => true,
            'terms' => $user->name,
        ];

        $this->json('POST', route('users.search'), $input)
            ->assertStatus(401);

        $this->signIn(User::factory()->create());

        $this->withoutExceptionHandling();
        $this->json('POST', route('users.search'), $input)
            ->assertStatus(403);

        $staff_profile_manager = User::factory()->create();
        $staff_profile_manager->addRole('staff-profiles-manager');
        $staff_profile_manager->refresh();
        $this->assertTrue($staff_profile_manager->can('viewAny', User::class));
        $this->signIn($staff_profile_manager);

        $this->withoutExceptionHandling();
        $this->json('POST', route('users.search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                 'name' => $user->name,
                 'class_name' => $user->class_name,
             ]);
    }

    public function test_a_user_can_get_banned_from_chatting()
    {
        $livestream = Livestream::factory()->create();
        $room = $livestream->chat_room;
        $message = $this->faker->sentence;
        $inquiry = Inquiry::factory()->create();
        $inquiry->saveLivestreams(['livestream' => $livestream]);
        $user = $inquiry->user;

        $this->json('POST', route('users.ban', ['id' => $user->id]))
            ->assertStatus(401);

        $this->signIn($user);

        // create message

        $input = [
            'room' => $room,
            'message' => $message,
        ];

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
             ]);

        $chat = Chat::all()->last();
        $this->assertEquals($message, $chat->message);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonFragment([
                 'id' => $chat->id,
                 'message' => $chat->message,
             ]);

        // ban

        $this->json('POST', route('users.ban', ['id' => $user->id]))
            ->assertStatus(403);

        $this->signInAdmin();

        Event::fake();

        $this->json('POST', route('users.ban', ['id' => $user->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => $user->name.' Banned',
             ]);

        $user->refresh();
        $this->assertNotNull($user->banned_at);

        Event::assertDispatched(function (UserBanned $event) use ($user) {
            return $event->user->id === $user->id;
        });

        // banned messages are soft deleted
        $this->assertFalse(Chat::all()->contains('message', $message));

        $this->signIn($user);

        // create message

        $input = [
            'room' => $room,
            'message' => $message,
        ];

        $this->json('POST', route('chat.send-message'), $input)
             ->assertStatus(403);
    }

    public function test_a_mod_can_ban_a_user_from_chatting()
    {
        $livestream = Livestream::factory()->create();
        $room = $livestream->chat_room;
        $message = $this->faker->sentence;
        $inquiry = Inquiry::factory()->create();
        $inquiry->saveLivestreams(['livestream' => $livestream]);
        $user = $inquiry->user;
        $moderator = User::factory()->create();
        $livestream->createPermission('moderate', $moderator);

        $this->json('POST', route('users.ban', ['id' => $user->id]))
            ->assertStatus(401);

        $this->signIn($user);

        // create message

        $input = [
            'room' => $room,
            'message' => $message,
        ];

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
             ]);

        $chat = Chat::all()->last();
        $this->assertEquals($message, $chat->message);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonFragment([
                 'id' => $chat->id,
                 'message' => $chat->message,
             ]);

        // ban

        $this->json('POST', route('users.ban', ['id' => $user->id]), ['room' => $room])
            ->assertStatus(403);

        $this->signIn($moderator);

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('users.ban', ['id' => $user->id]), ['room' => $room])
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => $user->name.' Banned',
             ]);

        $user->refresh();
        $this->assertNotNull($user->banned_at);

        Event::assertDispatched(function (UserBanned $event) use ($user) {
            return $event->user->id === $user->id;
        });

        // banned messages are soft deleted
        $this->assertFalse(Chat::all()->contains('message', $message));
    }

    public function test_a_user_can_verify_their_email_address()
    {
        $password = Str::random(8);
        $user = User::factory()->create([
            'email_verified_at' => null,
            'password' => Hash::make($password),
        ]);

        $url = $user->getEmailVerificationUrl();

        $this->get(route('users.verify-email', ['id' => $user->id]))
             ->assertStatus(401);

        $this->withoutExceptionHandling();

        $home_page = Page::find(1);
        $version = $home_page->version;
        $version->redirect = null;
        $version->save();
        $home_page->publish();
        $version->refresh();
        $home_page->refresh();

        $this->get($url)
             ->assertSuccessful()
             ->assertViewHas([
                'success' => 'Email Verification Complete',
             ]);

        $user->refresh();
        $this->assertNotNull($user->email_verified_at);

        $this->withoutExceptionHandling();
        $this->get($url)
             ->assertSuccessful()
             ->assertViewHas([
                'error' => 'Your email has already been confirmed',
            ]);
    }

    public function test_an_email_verification_can_be_requested()
    {
        Mail::fake();

        $user = User::factory()->create([
            'email_verified_at' => null,
        ]);

        $email = $user->email;

        $this->json('POST', route('users.send-email-verification', ['id' => $user->id]))
            ->assertStatus(401);

        $this->signIn($user);

        $this->withoutExceptionHandling();
        $this->json('POST', route('users.send-email-verification', ['id' => $user->id]))
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => 'Email Verification Sent',
            ]);

        Mail::assertQueued(EmailVerification::class, function ($mail) use ($email) {
            return $mail->hasTo($email);
        });

        $this->get($user->getEmailVerificationUrl())
             ->assertSuccessful()
             ->assertViewHas([
                'success' => 'Email Verification Complete',
             ]);

        $this->json('POST', route('users.send-email-verification', ['id' => $user->id]))
             ->assertStatus(422)
            ->assertJsonFragment([
                'error' => 'Your email has already been confirmed',
            ]);
    }

    public function test_a_user_can_send_a_password_reset_email()
    {
        $user = User::factory()->create([
            'oauth_id' => null,
        ]);

        $this->json('POST', route('users.request-password-reset'), [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([ 'email' ]);

        $this->json('POST', route('users.request-password-reset'), ['email' => Str::random(8).'@foobar.com'])
             ->assertStatus(422)
             ->assertJsonValidationErrors([ 'email' ]);

        Mail::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('users.request-password-reset'), ['email' => $user->email])
             ->assertSuccessful()
             ->assertJsonFragment([
                 'success' => 'Password Reset Email Sent',
             ]);

        Mail::assertQueued(ResetPassword::class, function ($mail) use ($user) {
            return $mail->hasTo($user->email);
        });
    }

    public function test_a_user_can_reset_their_password()
    {
        $user = User::factory()->create();
        $new_password = Str::random(8);

        $url = $user->getResetPasswordUrl();

        $this->get($url)
             ->assertSuccessful();

        $this->json('POST', route('users.reset-password', ['id' => $user->id]))
            ->assertStatus(403);

        $this->json('POST', $url, [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'password',
             ]);

        $this->json('POST', $url, ['password' => $new_password])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'password',
             ]);

        $this->json('POST', $url, ['password' => $new_password, 'password_confirmation' => 'foobar'])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'password',
             ]);

        $this->withoutExceptionHandling();
        $this->json('POST', $url, ['password' => $new_password, 'password_confirmation' => $new_password])
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Password Reset',
             ]);

        $this->assertTrue(auth()->check());
        $this->assertEquals($user->id, auth()->user()->id);
    }

    public function test_a_user_with_oauth_cannot_reset_their_password()
    {
        $user = User::factory()->create([
            'oauth_id' => Str::random(8),
        ]);

        $this->json('POST', route('users.request-password-reset'), ['email' => $user->email])
             ->assertStatus(422)
            ->assertJsonFragment([
                'error' => 'Please reset your password via Google',
            ]);
    }

    public function test_a_user_from_inquiry_is_activated_when_they_reset_their_password()
    {
        $name = $this->faker->name;
        $email = $this->faker->safeEmail;

        $input = [
            'name' => $name,
            'email' => $email,
        ];

        $this->json('POST', route('inquiries.store'), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => 'Inquiry Saved',
            ])
            ->assertJsonStructure(['success', 'redirect']);

        $user = User::all()->last();
        $this->assertEquals($email, $user->email);

        $this->assertNull($user->activated_at);

        $new_password = Str::random(8);

        $url = $user->getResetPasswordUrl();

        $this->get($url)
             ->assertSuccessful();

        $this->withoutExceptionHandling();
        $this->json('POST', $url, ['password' => $new_password, 'password_confirmation' => $new_password])
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Password Reset',
             ]);

        $user->refresh();

        $this->assertTrue(auth()->check());
        $this->assertEquals($user->id, auth()->user()->id);
        $this->assertNotNull($user->activated_at);
    }

    public function test_a_user_is_activated_if_they_create_a_password_from_the_inquiry_form()
    {
        $name = $this->faker->name;
        $email = $this->faker->safeEmail;
        $password = Str::random(8);

        $input = [
            'name' => $name,
            'email' => $email,
            'password' => $password,
            'password_confirmation' => $password,
        ];

        $this->withoutExceptionHandling();
        $this->json('POST', route('inquiries.store'), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => 'Inquiry Saved',
            ]);

        $inquiry = Inquiry::all()->last();

        $this->assertInstanceOf(Inquiry::class, $inquiry);

        $this->assertEquals($name, $inquiry->user->name);
        $this->assertEquals($email, $inquiry->user->email);

        $this->assertTrue(auth()->attempt(['email' => $email, 'password' => $password]));

        $user = User::all()->last();

        $this->assertEquals($email, $user->email);
        $this->assertNotNull($user->activated_at);
    }

    public function test_a_user_is_activated_if_they_login()
    {
        $user = User::factory()->create([
            'password' => bcrypt('password'),
            'activated_at' => null,
        ]);

        $this->json('POST', route('login'), ['email' => $user->email, 'password' => 'password'])
             ->assertJsonFragment([
                'redirect' => '/?success=Login%20Successful',
             ])
            ->assertSessionHas('timeout');

        $user->refresh();
        $this->assertNotNull($user->activated_at);
    }

    public function test_an_email_address_can_check_if_its_activated()
    {
        // Using this to determine whether to display create a password checkbox on the inquiry form

        $user = User::factory()->create();

        $this->json('POST', route('users.activation-check'), [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'email',
             ]);

        $this->json('POST', route('users.activation-check'), ['email' => $user->email])
             ->assertSuccessful()
            ->assertJsonFragment([
                'activated_at' => true,
            ]);

        $user = User::factory()->create([
            'activated_at' => null,
        ]);

        $this->json('POST', route('users.activation-check'), ['email' => $user->email])
             ->assertSuccessful()
            ->assertJsonFragment([
                'activated_at' => false,
            ]);
    }

    public function test_editing_can_be_toggled_for_a_user()
    {
        $page = Page::factory()->create();
        $this->post(route('editing-toggle'), ['page_type' => 'page', 'page_id' => $page->id])
             ->assertRedirect('/login');

        $this->signIn(User::factory()->create());

        $this->post(route('editing-toggle'), ['page_type' => 'page', 'page_id' => $page->id, 'enable' => true])
             ->assertStatus(403);

        $this->signInAdmin();

        $this->post(route('editing-toggle'), ['page_type' => 'page', 'page_id' => $page->id])
            ->assertStatus(302);

        $this->post(route('editing-toggle'), ['page_type' => 'page', 'page_id' => $page->id, 'enable' => true])
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Editing Enabled',
                'editing' => true,
             ]);

        $this->assertTrue(auth()->user()->editing);

        $this->post(route('editing-toggle'), ['page_type' => 'page', 'page_id' => $page->id, 'enable' => false])
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Editing Disabled',
                'editing' => false,
             ]);

        $this->assertFalse(auth()->user()->editing);
    }

    public function test_dark_mode_can_be_toggled_for_a_user()
    {
        $page = Page::factory()->create();
        $this->post(route('dark-toggle'))
             ->assertRedirect('/login');

        $user = User::factory()->create();
        $this->signIn($user);

        $this->post(route('dark-toggle'))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Dark Mode Enabled',
                'enabled' => true,
             ]);

        $this->assertTrue(auth()->user()->dark_mode);

        $this->post(route('dark-toggle'))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Dark Mode Disabled',
                'enabled' => false,
             ]);

        $this->assertFalse(auth()->user()->dark_mode);
    }

    public function test_the_current_user_can_be_loaded()
    {
        $user = User::factory()->create();
        $role = Role::factory()->create();
        $user->addRole($role);
        $staff_profile = StaffProfile::factory()->create([
            'user_id' => $user->id,
        ]);

        $this->assertNotNull($user->staffProfile);

        $this->json('GET', route('users.current'))
            ->assertStatus(401);

        $this->signIn($user);

        $this->json('GET', route('users.current'))
             ->assertSuccessful()
             ->assertJsonFragment([
                 'id' => $user->id,
                 'name' => $user->name,
                 'name' => $role->name,
                 'bio' => $user->staffProfile->bio,
             ]);
    }

    public function test_a_guardian_is_assigned_the_guardian_role_on_login()
    {
        $guardian = User::where('email', 'ulrika.drevniok@gmail.com')->first();

        if (!$guardian) {
            $guardian = User::factory()->create([
                'email' => 'ulrika.drevniok@gmail.com',
                'password' => bcrypt('password'),
            ]);
        }

        $user = User::factory()->create();

        $this->assertFalse($user->isGuardian());
        $this->assertTrue($guardian->isGuardian());

        $this->json('POST', route('login'), ['email' => $guardian->email, 'password' => 'password'])
             ->assertJsonFragment([
                'redirect' => '/?success=Login%20Successful',
             ])
            ->assertSessionHas('timeout');

        $guardian->refresh();

        $this->assertTrue($guardian->hasRole('guardian'));
    }
}
