<?php

namespace Tests\Feature;

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

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

use App\Models\Photo;
use App\Models\Page;
use App\Models\ContentElement;
use App\Models\PhotoBlock;
use App\Models\FileUpload;
use App\Models\User;
use Tests\Feature\SoftDeletesTestTrait;
use App\Models\Livestream;
use App\Models\Role;
use App\Models\Tag;

class PhotoTest extends TestCase
{
    use SoftDeletesTestTrait;

    protected function getModel()
    {
        return Photo::factory()
                    ->for(PhotoBlock::factory(), 'content')
                    ->for(FileUpload::factory()->jpg(), 'fileUpload')
                    ->create();
    }

    public function test_a_photo_can_be_updated()
    {
        $page = Page::factory()->create();
        $content_element = ContentElement::factory()
                                ->for(PhotoBlock::factory()->has(Photo::factory()->for(FileUpload::factory()->jpg(), 'fileUpload')), 'content')
                                ->create();
        $content_element->pages()->detach();
        $content_element->pages()->attach($page, ['sort_order' => 1, 'unlisted' => false, 'randomize' => false, 'version_id' => $page->draft_version_id]);
        $photo_block = $content_element->content;
        $photo = $photo_block->photos->first();
        $this->assertInstanceOf(Photo::class, $photo);

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

        $input = Photo::factory()->raw([
            'hide_print' => true,
            'hide_mobile' => true,
            'large_link' => true,
        ]);
        $input['file_upload_id'] = $file_upload->id;

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

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

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

        $this->signInAdmin();

        $this->json('POST', route('photos.update', ['id' => $photo->id]), [])
            ->assertStatus(422)
            ->assertJsonValidationErrors([
                'file_upload_id',
                'sort_order',
                'span',
                'vertical_span',
                'offsetX',
                'offsetY',
                'enlarge',
                'hide_print',
                'hide_mobile',
                'large_link',
                'border',
            ]);

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

        $photo->refresh();

        $this->assertTrue(Arr::get($input, 'enlarge'));

        $this->assertEquals(Arr::get($input, 'file_upload_id'), $photo->fileUpload->id);
        $this->assertEquals(Arr::get($input, 'name'), $photo->name);
        $this->assertEquals(Arr::get($input, 'alt'), $photo->alt);
        $this->assertEquals(Arr::get($input, 'sort_order'), $photo->sort_order);
        $this->assertEquals(Arr::get($input, 'span'), $photo->span);
        $this->assertEquals(Arr::get($input, 'vertical_span'), $photo->vertical_span);
        $this->assertEquals(Arr::get($input, 'offsetX'), $photo->offsetX);
        $this->assertEquals(Arr::get($input, 'offsetY'), $photo->offsetY);
        $this->assertEquals(Arr::get($input, 'enlarge'), $photo->enlarge);
        $this->assertEquals(Arr::get($input, 'hide_print'), $photo->hide_print);
        $this->assertEquals(Arr::get($input, 'hide_mobile'), $photo->hide_mobile);
        $this->assertEquals(Arr::get($input, 'large_link'), $photo->large_link);
        $this->assertEquals(Arr::get($input, 'border'), $photo->border);
        $this->assertEquals(Arr::get($input, 'number'), $photo->number);
    }

    public function test_a_user_that_has_update_for_a_content_element_can_remove_photos()
    {
        $content_element = $this->createContentElement(PhotoBlock::factory()->has(Photo::factory([
            'file_upload_id' => FileUpload::factory()->jpg(),
        ]), 'photos'));
        $photo_block = $content_element->content;
        $photo = $photo_block->photos()->first();
        $page = $content_element->pages->first();

        $this->assertInstanceOf(Photo::class, $photo);

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

        $user->createPermission('update', $page);

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

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

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

        $this->signIn($user);

        $this->json('POST', route('photos.remove', ['id' => $photo->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Photo Removed',
             ]);

        $livestream = Livestream::factory()->create();

        $photo = Photo::factory()->create([
            'file_upload_id' => FileUpload::factory()->jpg(),
            'content_id' => $livestream->id,
            'content_type' => get_class($livestream),
        ]);

        $user->addRole('livestreams-manager');

        $user->refresh();

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

        $this->json('POST', route('photos.remove', ['id' => $photo->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Photo Removed',
             ]);
    }

    public function test_all_photos_can_be_listed_for_upload_selection()
    {
        $file_upload = FileUpload::factory()->jpg()->create();
        $content_element = $this->createContentElement(PhotoBlock::factory()->has(Photo::factory([
            'file_upload_id' => $file_upload->id,
        ]), 'photos'));
        $photo_block = $content_element->content;
        $photo = $photo_block->photos()->first();
        $page = $content_element->pages->first();
        $page->refresh();

        $this->json('POST', route('photos.paginate'), ['paginate_count' => Photo::all()->count()])
             ->assertStatus(401);

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

        $this->json('POST', route('photos.paginate'), ['paginate_count' => Photo::all()->count()])
             ->assertStatus(403);

        $user->addRole('pages-editor');
        $this->enableEditing();
        $user->refresh();

        $this->assertFalse($user->can('view', $content_element));

        cache()->tags(['photos'])->flush();

        $this->json('POST', route('photos.paginate'), ['paginate_count' => Photo::all()->count()])
             ->assertSuccessful()
             ->assertJsonMissing([
                'name' => $photo->name,
             ]);

        $page->createPermission('update', $user);
        $user->addRole('admin');
        $page->refresh();
        $user->refresh();

        $this->assertTrue($page->contentElements()->count() > 0);
        $this->assertTrue($page->content->count() > 0);

        $this->assertTrue($user->can('view', $content_element));
        $this->assertTrue($user->can('view', $photo->content->contentElement));

        cache()->tags(['photos'])->flush();

        $this->json('POST', route('photos.paginate'), ['paginate_count' => 10])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $photo->name,
             ]);
    }

    public function test_a_photo_can_be_hidden_from_upload_listings()
    {
        $this->signInAdmin();
        $this->enableEditing();

        $file_upload = FileUpload::factory()->jpg()->create();
        $content_element = $this->createContentElement(PhotoBlock::factory());
        $photo_block = $content_element->content;
        $photo = Photo::factory([
            'file_upload_id' => $file_upload->id,
            'content_id' => $photo_block->id,
            'content_type' => get_class($photo_block),
        ])->create();

        $content_element2 = $this->createContentElement(PhotoBlock::factory());
        $photo_block2 = $content_element2->content;
        $photo2 = Photo::factory([
            'file_upload_id' => $file_upload->id,
            'content_id' => $photo_block2->id,
            'content_type' => get_class($photo_block2),
        ])->create();

        $page = $content_element->pages->first();
        $page->refresh();

        cache()->tags(['photos'])->flush();

        $this->json('POST', route('photos.paginate'), ['paginate_count' => 2])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $photo2->name,
             ]);

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

        $this->json('POST', route('photos.hide', ['id' => $photo->id]))
             ->assertStatus(403);

        $this->signInAdmin();
        $this->enableEditing();

        $this->json('POST', route('photos.hide', ['id' => $photo->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Photo Hidden',
             ]);

        cache()->tags(['photos'])->flush();

        $photo->refresh();
        $photo2->refresh();
        $this->assertNotNull($photo->hidden_at);
        $this->assertNotNull($photo2->hidden_at);

        $this->json('POST', route('photos.paginate'))
             ->assertSuccessful()
             ->assertJsonMissing([
                'name' => $photo2->name,
             ]);

        $this->json('POST', route('photos.hide', ['id' => $photo->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Photo Unhidden',
             ]);

        cache()->tags(['photos'])->flush();

        $this->json('POST', route('photos.paginate'))
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $photo2->name,
             ]);
    }

    public function test_a_photo_can_save_tags()
    {
        $tag = Tag::factory()->create();

        $content_element = $this->createContentElement(PhotoBlock::factory()->has(Photo::factory([
            'file_upload_id' => FileUpload::factory()->jpg(),
        ]), 'photos'));
        $photo_block = $content_element->content;
        $photo = $photo_block->photos()->first();

        $input = $photo->toArray();
        $input['tags'] = [
            $tag->toArray(),
        ];

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

        $this->signIn($user);
        $this->assertFalse($user->can('update', $photo));

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

        $user->addRole('pages-editor');

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

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

        $photo->refresh();
        $this->assertNotNull($photo->tags);
        $this->assertEquals(1, $photo->tags->count());
        $this->assertEquals($tag->id, $photo->tags->first()->id);
    }

    public function test_photos_can_be_filtered_by_tag()
    {
        $file_upload = FileUpload::factory()->jpg()->create();
        $content_element = $this->createContentElement(PhotoBlock::factory()->has(Photo::factory([
            'file_upload_id' => $file_upload->id,
        ]), 'photos'));
        $photo_block = $content_element->content;
        $photo = $photo_block->photos()->first();
        $page = $content_element->pages->first();
        $page->refresh();

        $this->signInAdmin();
        $this->enableEditing();

        cache()->tags(['photos'])->flush();
        $this->json('POST', route('photos.paginate'), ['paginate_count' => 50])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $photo->name,
             ]);

        $tag1 = Tag::factory()->create();
        $tag2 = Tag::factory()->create();

        $photo->addTag($tag1);
        $photo->refresh();

        $this->json('POST', route('photos.paginate'), ['paginate_count' => Photo::all()->count(), 'terms' => $tag1->name])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $photo->name,
             ]);

        $this->json('POST', route('photos.paginate'), ['paginate_count' => Photo::all()->count(), 'terms' => $tag2->name])
             ->assertSuccessful()
             ->assertJsonMissing([
                'name' => $photo->name,
             ]);
    }

    public function test_a_photo_can_be_downloaded()
    {
        $file_upload = FileUpload::all()->first();
        $content_element = $this->createContentElement(PhotoBlock::factory()->has(Photo::factory([
            'file_upload_id' => $file_upload->id,
        ]), 'photos'));
        $photo_block = $content_element->content;
        $photo = $photo_block->photos()->first();
        $page = $content_element->pages->first();
        $page->refresh();

        $this->json('GET', route('photos.download', ['id' => $photo->id]))
            ->assertStatus(401);

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

        $this->json('GET', route('photos.download', ['id' => $photo->id]))
            ->assertStatus(403);

        $user->createPermission('update', $page);
        $user->refresh();
        $page->refresh();

        $this->assertTrue($user->can('download', $photo));

        $this->json('GET', route('photos.download', ['id' => $photo->id]))
            ->assertSuccessful();
    }
}
