<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\UploadedFile;

use App\Models\StaffProfile;
use App\Models\User;
use App\Models\FileUpload;
use App\Models\Photo;
use App\Models\Tag;
use App\Models\Role;

use Tests\Feature\SoftDeletesTestTrait;

class StaffProfileTest extends TestCase
{
    use SoftDeletesTestTrait;
    protected function getModel()
    {
        return StaffProfile::factory()->create();
    }

    protected function getClassname()
    {
        return 'staff-profile';
    }

    public function test_a_staff_profile_can_be_created()
    {
        $input = StaffProfile::factory()->raw();

        Storage::fake();
        $file_name = Str::random().'.jpg';
        $file = UploadedFile::fake()->image($file_name);
        $file_upload = (new FileUpload())->saveFile($file, 'photos', true);

        $photo_input = Photo::factory()->raw();
        $photo_input['file_upload_id'] = $file_upload->id;

        $input['photos'] = [$photo_input];

        $tag = Tag::factory()->create();

        $input['tags'] = [$tag];

        $this->json('POST', route('staff-profiles.store'), [])
            ->assertStatus(401);

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

        $this->json('POST', route('staff-profiles.store'), [])
            ->assertStatus(403);

        $this->signInAdmin();

        $this->json('POST', route('staff-profiles.store'), [])
            ->assertStatus(422)
             ->assertJsonValidationErrors([
                 'first_name',
                 'last_name',
             ]);

        $this->withoutExceptionHandling();
        $this->json('POST', route('staff-profiles.store'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'first_name').' '.Arr::get($input, 'last_name').' Saved',
             ]);

        $staff_profile = StaffProfile::all()->last();

        $this->assertNotNull($staff_profile->first_name);
        $this->assertEquals(Arr::get($input, 'first_name'), $staff_profile->first_name);
        $this->assertNotNull($staff_profile->last_name);
        $this->assertEquals(Arr::get($input, 'last_name'), $staff_profile->last_name);
        $this->assertNotNull($staff_profile->bio_draft);
        $this->assertEquals(Arr::get($input, 'bio_draft'), $staff_profile->bio_draft);
        $this->assertNotNull($staff_profile->credentials);
        $this->assertEquals(Arr::get($input, 'credentials'), $staff_profile->credentials);
        $this->assertNotNull($staff_profile->departments);
        $this->assertEquals(Arr::get($input, 'departments'), $staff_profile->departments);

        $this->assertEquals(1, $staff_profile->photos->count());
        $photo = $staff_profile->photos->first();

        $this->assertInstanceOf(Photo::class, $photo);
        $this->assertEquals(Arr::get($photo_input, 'name'), $photo->name);
        $this->assertEquals(Arr::get($photo_input, 'description'), $photo->description);
        $this->assertEquals(Arr::get($photo_input, 'alt'), $photo->alt);
        $this->assertEquals($photo->fileUpload->id, $file_upload->id);

        $this->assertEquals(1, $staff_profile->tags->count());
        $this->assertEquals($tag->id, $staff_profile->tags->first()->id);

        $this->assertNotNull($staff_profile->user);
        $this->assertInstanceOf(User::class, $staff_profile->user);
        $this->assertEquals(Arr::get($input, 'user_id'), $staff_profile->user->id);
    }

    public function test_a_staff_profile_can_be_updated()
    {
        $staff_profile = StaffProfile::factory()->has(Photo::factory([
            'file_upload_id' => FileUpload::factory()->jpg(),
        ]), 'photos')->has(Tag::factory(), 'tags')->create();

        $old_tag = $staff_profile->tags()->first();
        $old_photo = $staff_profile->photos()->first();
        $old_user = $staff_profile->user;

        $this->assertNotNull($old_tag);
        $this->assertNotNull($old_photo);
        $this->assertNotNull($old_user);

        $this->json('POST', route('staff-profiles.update', ['id' => $staff_profile->id]), [])
            ->assertStatus(401);

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

        $this->json('POST', route('staff-profiles.update', ['id' => $staff_profile->id]), [])
            ->assertStatus(403);

        $this->signInAdmin();

        $this->json('POST', route('staff-profiles.update', ['id' => $staff_profile->id]), [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                 'first_name',
                 'last_name',
             ]);

        $input = StaffProfile::factory()->raw();
        $input['id'] = $staff_profile->id;

        Storage::fake();
        $file_name = Str::random().'.jpg';
        $file = UploadedFile::fake()->image($file_name);
        $file_upload = (new FileUpload())->saveFile($file, 'photos', true);

        $photo_input = Photo::factory()->raw();
        $photo_input['file_upload_id'] = $file_upload->id;

        $input['photos'] = [$photo_input];

        $tag = Tag::factory()->create();

        $input['tags'] = [$tag];

        $this->json('POST', route('staff-profiles.update', ['id' => $staff_profile->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'first_name').' '.Arr::get($input, 'last_name').' Saved',
             ]);

        $staff_profile->refresh();

        $this->assertEquals(Arr::get($input, 'first_name'), $staff_profile->first_name);
        $this->assertEquals(Arr::get($input, 'last_name'), $staff_profile->last_name);
        $this->assertEquals(Arr::get($input, 'bio_draft'), $staff_profile->bio_draft);
        $this->assertEquals(Arr::get($input, 'credentials'), $staff_profile->credentials);
        $this->assertEquals(Arr::get($input, 'departments'), $staff_profile->departments);

        $this->assertEquals(1, $staff_profile->photos->count());
        $photo = $staff_profile->photos->first();

        $this->assertInstanceOf(Photo::class, $photo);
        $this->assertEquals(Arr::get($photo_input, 'name'), $photo->name);
        $this->assertEquals(Arr::get($photo_input, 'description'), $photo->description);
        $this->assertEquals(Arr::get($photo_input, 'alt'), $photo->alt);
        $this->assertEquals($photo->fileUpload->id, $file_upload->id);

        $this->assertEquals(1, $staff_profile->tags->count());
        $this->assertEquals($tag->id, $staff_profile->tags->first()->id);

        $this->assertNotNull($staff_profile->user);
        $this->assertInstanceOf(User::class, $staff_profile->user);
        $this->assertEquals(Arr::get($input, 'user_id'), $staff_profile->user->id);
    }

    public function test_the_staff_profiles_index_can_be_viewed()
    {
        $this->get(route('staff-profiles.manage'))
            ->assertRedirect('/login');

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

        $this->get(route('staff-profiles.manage'))
             ->assertRedirect('/');

        $this->signInAdmin();

        $this->get(route('staff-profiles.manage'))
             ->assertSuccessful();
    }

    public function test_staff_profiles_can_be_loaded_for_pagination()
    {
        $staff_profile = StaffProfile::factory()->create();

        $this->withoutExceptionHandling();
        $this->json('POST', route('staff-profiles.paginate'), ['paginate' => 'true', 'paginate_count' => StaffProfile::count(), 'sort_by' => 'last_name'])
             ->assertSuccessful()
             ->assertJsonFragment([
                'first_name' => $staff_profile->first_name,
                'last_name' => $staff_profile->last_name,
             ]);
    }

    public function test_staff_profiles_can_be_searched_for()
    {
        $staff_profile = StaffProfile::factory()->create();
        $staff_profile2 = StaffProfile::factory()->create();

        $this->assertNotNull($staff_profile->full_name);

        $input = [
            'terms' => $staff_profile->full_name,
        ];

        $this->withoutExceptionHandling();
        $this->json('POST', route('staff-profiles.search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'first_name' => $staff_profile->first_name,
                'last_name' => $staff_profile->last_name,
             ])
             ->assertJsonMissing([
                'first_name' => $staff_profile2->first_name,
                'last_name' => $staff_profile2->last_name,
             ]);
    }

    public function test_no_search_results_doesnt_break()
    {
        $input = [
            'terms' => Str::random(40),
        ];

        $this->withoutExceptionHandling();
        $this->json('POST', route('staff-profiles.search'), $input)
             ->assertSuccessful();
    }

    public function test_users_can_update_their_profile_but_not_user_id()
    {
        $user = User::factory()->create();

        $staff_profile = StaffProfile::factory()->create([
            'user_id' => $user->id,
        ]);

        $this->assertTrue($user->can('update', $staff_profile));

        $input = StaffProfile::factory()->raw();

        $this->signIn($user);

        $this->json('POST', route('staff-profiles.update', ['id' => $staff_profile->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'first_name').' '.Arr::get($input, 'last_name').' Saved',
             ]);

        $staff_profile->refresh();

        $this->assertEquals(Arr::get($input, 'first_name'), $staff_profile->first_name);
        $this->assertEquals(Arr::get($input, 'last_name'), $staff_profile->last_name);
        $this->assertEquals(Arr::get($input, 'bio_draft'), $staff_profile->bio_draft);
        $this->assertEquals(Arr::get($input, 'credentials'), $staff_profile->credentials);
        $this->assertEquals(Arr::get($input, 'departments'), $staff_profile->departments);

        $this->assertEquals($user->id, $staff_profile->user_id);
    }

    public function test_soft_deleted_staff_profiles_can_be_viewed()
    {
        $staff_profile = StaffProfile::all()->sortBy(function($staff_profile) {
            return $staff_profile->last_name;
        })->take(10)->random();

        $this->json('POST', route('staff-profiles.paginate'), ['paginate' => 'true', 'sort_by' => 'last_name'])
             ->assertSuccessful()
             ->assertJsonFragment([
                'first_name' => $staff_profile->first_name,
                'last_name' => $staff_profile->last_name,
             ]);

        $this->json('POST', route('staff-profiles.remove', ['id' => $staff_profile->id]))
            ->assertStatus(401);

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

        $this->json('POST', route('staff-profiles.remove', ['id' => $staff_profile->id]))
            ->assertStatus(403);

        $this->signInAdmin();

        $this->withoutExceptionHandling();

        $this->json('POST', route('staff-profiles.remove', ['id' => $staff_profile->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Staff Profile Removed',
             ]);

        $this->json('POST', route('staff-profiles.paginate'), ['paginate' => 'true', 'sort_by' => 'last_name'])
             ->assertSuccessful()
             ->assertJsonMissing([
                'full_name' => $staff_profile->full_name,
             ]);

        $this->json('POST', route('staff-profiles.paginate'), ['paginate' => 'true', 'sort_by' => 'last_name', 'deleted' => 'true'])
             ->assertSuccessful()
             ->assertJsonFragment([
                'full_name' => $staff_profile->full_name,
             ]);
    }

    public function test_a_user_can_view_and_update_their_staff_profile()
    {
        $staff_profile = StaffProfile::factory()->create();

        $this->get(route('staff-profiles.edit', ['id' => $staff_profile]))
             ->assertRedirect(route('login'));

        $this->json('GET', route('staff-profiles.edit', ['id' => $staff_profile->id]))
            ->assertStatus(401);

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

        $this->get(route('staff-profiles.edit', ['id' => $staff_profile]))
            ->assertRedirect();

        $this->json('GET', route('staff-profiles.edit', ['id' => $staff_profile->id]))
            ->assertStatus(403);

        $this->signIn($staff_profile->user);

        $this->get(route('staff-profiles.edit', ['id' => $staff_profile]))
            ->assertSuccessful();

        $this->json('GET', route('staff-profiles.edit', ['id' => $staff_profile->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'email' => $staff_profile->user->email,
             ]);

        $input = StaffProfile::factory()->raw();

        $this->json('POST', route('staff-profiles.update', ['id' => $staff_profile->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'first_name').' '.Arr::get($input, 'last_name').' Saved',
             ]);

        $staff_profile->refresh();

        $this->assertEquals(Arr::get($input, 'first_name'), $staff_profile->first_name);
        $this->assertEquals(Arr::get($input, 'last_name'), $staff_profile->last_name);
        $this->assertEquals(Arr::get($input, 'bio_draft'), $staff_profile->bio_draft);
        $this->assertEquals(Arr::get($input, 'credentials'), $staff_profile->credentials);
        $this->assertEquals(Arr::get($input, 'departments'), $staff_profile->departments);
    }

    public function test_a_staff_profile_can_be_published()
    {
        $staff_profile = StaffProfile::factory()->create([
            'bio' => null,
        ]);

        $this->json('POST', route('staff-profiles.publish', ['id' => $staff_profile->id]))
            ->assertStatus(401);

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

        $this->signIn($user);

        $this->json('POST', route('staff-profiles.publish', ['id' => $staff_profile->id]))
            ->assertStatus(403);

        $user->addRole('staff-profiles-manager');
        $user->refresh();

        $this->assertTrue($user->can('publish', $staff_profile));
        $old_published_at = $staff_profile->published_at;

        $this->assertNotEquals($staff_profile->bio, $staff_profile->bio_draft);

        $this->json('POST', route('staff-profiles.publish', ['id' => $staff_profile->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => $staff_profile->full_name.' Published',
             ]);

        $staff_profile->refresh();
        $this->assertEquals($staff_profile->bio, $staff_profile->bio_draft);
        $this->assertNotNull($staff_profile->published_at);
        $this->assertNotEquals($old_published_at, $staff_profile->published_at);
    }

    public function test_a_user_can_load_their_staff_profile_page()
    {
        $staff_profile = StaffProfile::factory()->create();
        $user = $staff_profile->user;

        $this->get(route('staff-profiles.my-profile'))
            ->assertRedirect();

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

        $this->get(route('staff-profiles.my-profile'))
            ->assertRedirect();

        $this->signIn($user);

        $this->assertTrue($user->can('update', $staff_profile));

        $this->get(route('staff-profiles.my-profile'))
             ->assertSuccessful()
             ->assertViewHas('staff_profile', $staff_profile);
    }

    public function test_if_a_user_is_in_the_sg_staff_a_profile_is_created_if_required_when_visiting_my_profile()
    {
        $user = User::factory()->create();

        $this->get(route('staff-profiles.my-profile'))
            ->assertRedirect();

        $this->signIn($user);

        $this->get(route('staff-profiles.my-profile'))
            ->assertRedirect();

        if (!Role::where('name', 'sg.staff')->first()) {
            $role = new Role();
            $role->name = 'sg.staff';
            $role->save();
        }

        $user->addRole('sg.staff');
        $user->refresh();

        $this->get(route('staff-profiles.my-profile'))
             ->assertSuccessful()
             ->assertViewHas('staff_profile', $user->refresh()->staffProfile);

        $staff_profile = StaffProfile::all()->last();
        $this->assertEquals($user->first_name, $staff_profile->first_name);
        $this->assertEquals($user->last_name, $staff_profile->last_name);
        $this->assertEquals($user->id, $staff_profile->user_id);
    }
}
