<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Queue;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

use App\Models\Photo;
use App\Models\PhotoBlock;
use App\Models\FileUpload;
use App\Models\TextBlock;
use App\Models\Page;
use App\Utilities\PageLink;
use App\Models\ContentElement;
use App\Models\User;
use App\Models\Role;

use Tests\Unit\PageLinkTestTrait;

use App\Jobs\CreatePhotoImage;

class PhotoTest extends TestCase
{
    protected function createPhoto()
    {
        Storage::fake();
        $file_name = Str::random().'jpg';
        $file = UploadedFile::fake()->image($file_name);
        $file_upload = (new FileUpload())->saveFile($file, 'photos', true);

        $input = Photo::factory()->titles()->link()->raw([
            'file_upload_id' => $file_upload->id,
        ]);

        $this->assertTrue(Storage::exists($file_upload->storage_filename));

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);
        $this->assertInstanceOf(FileUpload::class, $photo->fileUpload);
        $this->assertTrue(Storage::exists($photo->fileUpload->storage_filename));
        return $photo;
    }

    public function test_a_photo_has_a_file_upload()
    {
        cache()->flush();
        $photo = $this->createPhoto();
        $this->assertInstanceOf(FileUpload::class, $photo->fileUpload);
    }

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

        $input = Photo::factory()->titles()->link()->raw([
            'file_upload_id' => $file_upload->id,
        ]);

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);

        $this->assertEquals($photo_block->id, $photo->content->id);
        $this->assertEquals(get_class($photo_block), get_class($photo->content));
        $this->assertEquals(Arr::get($input, 'name'), $photo->name);
        $this->assertEquals(Arr::get($input, 'description'), $photo->description);
        $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, 'offsetX'), $photo->offsetX);
        $this->assertEquals(Arr::get($input, 'offsetY'), $photo->offsetY);
        $this->assertEquals(Arr::get($input, 'fill'), $photo->fill);
        $this->assertEquals(Arr::get($input, 'title'), $photo->title);
        $this->assertEquals(Arr::get($input, 'subtitle'), $photo->subtitle);
        $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, 'number'), $photo->number);
        $this->assertEquals(PageLink::convertLink(Arr::get($input, 'link')), $photo->link);
    }

    public function test_a_photo_belongs_to_a_content_item()
    {
        $photo = Photo::factory()->for(PhotoBlock::factory(), 'content')->create([
            'file_upload_id' => FileUpload::factory()->jpg(),
        ]);
        $this->assertInstanceOf(PhotoBlock::class, $photo->content);
    }

    public function test_a_photo_can_have_a_small()
    {
        Queue::fake();
        $photo = $this->createPhoto();
        $small = $photo->small;
        Queue::assertPushed(function (CreatePhotoImage $job) use ($photo) {
            return $job->photo->id === $photo->id;
        });

        CreatePhotoImage::dispatchSync($photo, 'small');
        $photo->createImage(100, 'small');
        $this->assertNotNull($photo->small);
        $this->assertInstanceOf(FileUpload::class, $photo->fileUpload);
        $photo->refresh();
        $this->assertNotEquals('/public/images/default.png', $photo->small);

        $this->assertTrue(strpos($photo->small, $photo->fileUpload->filename) > 0);
        //$this->assertTrue(Str::endsWith($small, '.jpg'));
        Storage::disk('public')->assertExists($small);
    }

    public function test_a_photos_images_can_be_removed()
    {
        Queue::fake();
        $photo = $this->createPhoto();
        $photo->createImage(100, 'small');
        $photo->createImage(100, 'medium');
        $photo->createImage(100, 'large');
        $photo->createImage(100, 'retina');
        $photo->refresh();
        $this->assertNotNull($photo->small);
        $this->assertNotNull($photo->medium);
        $this->assertNotNull($photo->large);
        $this->assertNotNull($photo->retina);
        $small = $photo->small;
        $medium = $photo->medium;
        $large = $photo->large;
        $retina = $photo->retina;
        Storage::disk('public')->assertExists($small);
        Storage::disk('public')->assertExists($medium);
        Storage::disk('public')->assertExists($large);
        Storage::disk('public')->assertExists($retina);
        $photo->removeImages();
        Storage::disk('public')->assertMissing($large);
        Storage::disk('public')->assertMissing($small);
        Storage::disk('public')->assertMissing($medium);
        Storage::disk('public')->assertMissing($retina);
    }

    public function test_a_photo_can_have_a_medium_image()
    {
        $photo = $this->createPhoto();
        $photo->createImage(100, 'medium');
        $medium = $photo->medium;
        $this->assertTrue(strpos($photo->medium, $photo->fileUpload->filename) > 0);
        //$this->assertTrue(Str::endsWith($medium, '.jpg'));
        Storage::disk('public')->assertExists($medium);
    }

    public function test_a_photo_can_have_a_large_image()
    {
        $photo = $this->createPhoto();
        $photo->createImage(100, 'large');
        $large = $photo->large;
        $this->assertTrue(strpos($photo->large, $photo->fileUpload->filename) > 0);
        //$this->assertTrue(Str::endsWith($large, '.jpg'));
        Storage::disk('public')->assertExists($large);
    }

    public function test_a_photo_can_have_a_retina_image()
    {
        $photo = $this->createPhoto();
        $photo->createImage(100, 'retina');
        $retina = $photo->retina;
        $this->assertTrue(strpos($photo->retina, $photo->fileUpload->filename) > 0);
        //$this->assertTrue(Str::endsWith($retina, '.jpg'));
        Storage::disk('public')->assertExists($retina);
    }

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

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

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);

        $photo->createImage(100, 'small');
        $photo->createImage(100, 'medium');
        $photo->createImage(100, 'large');
        $photo->createImage(100, 'retina');
        $this->assertTrue(Str::contains($photo->small, $file_name));
        $this->assertTrue(Str::contains($photo->medium, $file_name));
        $this->assertTrue(Str::contains($photo->large, $file_name));
        $this->assertTrue(Str::contains($photo->retina, $file_name));

        $file_name2 = Str::lower(Str::random().'jpg');
        $file2 = UploadedFile::fake()->image($file_name2);
        $file_upload2 = (new FileUpload())->saveFile($file2, 'photos', true);

        $input['file_upload_id'] = $file_upload2->id;

        $photo = (new Photo())->savePhoto($input, $photo->id, $photo_block);

        $photo->refresh();

        $this->assertEquals($file_upload2->id, $photo->fileUpload->id);
        $photo->createImage(100, 'small');
        $photo->createImage(100, 'medium');
        $photo->createImage(100, 'large');
        $photo->createImage(100, 'retina');
        $this->assertTrue(Str::contains($photo->small, $file_name2));
        $this->assertTrue(Str::contains($photo->medium, $file_name2));
        $this->assertTrue(Str::contains($photo->large, $file_name2));
        $this->assertTrue(Str::contains($photo->retina, $file_name2));
    }

    public function test_a_photo_link_is_converted_to_a_slug()
    {
        $content_element = $this->createContentElement(TextBlock::factory());
        $text_block = $content_element->content;
        $page = $content_element->pages()->first();
        $page->publish();
        $page->refresh();

        $link = $page->id.'#c-'.$content_element->uuid;

        $this->assertInstanceOf(Page::class, $page);
        $photo = Photo::factory()->for(PhotoBlock::factory(), 'content')->create([
            'link' => $link,
            'file_upload_id' => FileUpload::factory()->jpg(),
        ]);

        $this->assertNotNull($photo->link);

        $this->assertEquals('/'.$page->full_slug.'#'.$content_element->anchor, $photo->link);
    }

    public function test_a_photo_can_be_duplicated_but_use_the_same_file_upload()
    {
        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([
            'file_upload_id' => $file_upload->id,
        ]);

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);

        $new_photo = (new Photo())->savePhoto($input, $photo->id, $photo_block, true);
        $this->assertInstanceOf(Photo::class, $new_photo);

        $this->assertEquals($photo->fileUpload->id, $new_photo->fileUpload->id);
        $this->assertNotEquals($photo->id, $new_photo->id);
    }

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

        $input = Photo::factory()->titles()->link()->raw([
            'file_upload_id' => $file_upload->id,
        ]);

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $photo2 = (new Photo())->savePhoto($input, $photo->id, $photo_block, true);

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

        $photo->refresh();
        $photo2->refresh();

        $this->assertNotEquals($photo->id, $photo2->id);
        $this->assertNotNull($photo->fileUpload);
        $this->assertNotNull($photo2->fileUpload);
        $this->assertEquals($photo->fileUpload->id, $photo2->fileUpload->id);
    }

    public function test_a_photo_uses_the_file_upload_name()
    {
        Storage::fake();
        $name = Str::random();
        $file_name = $name.'!@#$%^&*(),.jpg';
        $file = UploadedFile::fake()->image($file_name);
        $file_upload = (new FileUpload())->saveFile($file, 'photos', true);

        $input = Photo::factory()->titles()->link()->raw([
            'file_upload_id' => $file_upload->id,
        ]);

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);

        $this->assertEquals(Str::lower($name).'.jpg', $file_upload->name);

        $photo->createImage(100, 'small');
        $small = $photo->small;

        $this->assertTrue(Str::contains($small, $file_upload->name));
    }

    public function test_photos_can_be_refeshed()
    {
        Queue::fake();
        Storage::fake('public');
        $photo = $this->createPhoto();
        $this->assertNotNull($photo->large);
        Storage::disk('public')->assertExists($photo->large);

        $old_large = $photo->large;

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

        $photo->file_upload_id = $file_upload->id;
        $photo->save();

        cache()->tags([cache_name($photo)])->flush();

        $photo->regenerate();

        $photo->refresh();

        $this->assertNull($photo->small);
        $this->assertNull($photo->medium);
        $this->assertNotNull($photo->large);
        $this->assertNull($photo->retina);

        Queue::assertPushed(function (CreatePhotoImage $job) use ($photo) {
            return $job->photo->id === $photo->id && $job->size === 'small';
        });

        //$photo->createImage(100, 'large');
        Storage::disk('public')->assertExists($photo->large);
    }

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

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

        $input = Photo::factory()->titles()->link()->raw([
            'file_upload_id' => $file_upload->id,
        ]);

        $page = Page::factory()->create();

        $photo_block = $this->createContentElement(PhotoBlock::factory(), $page)->content;
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);

        $photos = Photo::getPhotos();

        $this->assertInstanceOf(Collection::class, $photos);
        $this->assertTrue($photos->contains('id', $photo->id));
    }

    public function test_a_photo_link_to_a_content_element_is_converted_for_the_front_end()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(PhotoBlock::factory()->withText(), $page);
        $page->publish();
        $content_element->refresh();
        $this->assertNotNull($content_element->content->header);
        $this->assertNotNull($content_element->anchor);

        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([
            'file_upload_id' => $file_upload->id,
            'link' => $page->id.'#c-'.$content_element->uuid,
        ]);

        $photo_block = PhotoBlock::factory()->create();
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);
        $this->assertNotNull($photo->link);

        $this->assertFalse(editing());
        $this->assertEquals('/'.$page->full_slug.'#'.$content_element->anchor, $photo->link);

        $photo->link = $page->id;
        $photo->save();
        $photo->refresh();

        $this->assertEquals('/'.$page->full_slug, $photo->link);

        $this->get($page->full_slug)
             ->assertSuccessful()
            ->assertSee($photo->link);

        $this->signInAdmin();
        $this->assertNotNull($page->preview_url);

        $params = substr($page->preview_url, strpos($page->preview_url, "?"));

        $this->get($page->preview_url)
             ->assertSuccessful();
        //->assertSee($params);
    }

    public function test_a_user_can_get_photos_that_are_permission_based()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(PhotoBlock::factory()->withText(), $page);

        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([
            'file_upload_id' => $file_upload->id,
            'link' => $page->id.'#c-'.$content_element->uuid,
        ]);

        $photo_block = $content_element->content;
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);

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

        $page->refresh();
        $this->assertInstanceOf(ContentElement::class, $photo->content->contentElement);
        $this->assertEquals(1, $page->contentElements()->count());

        $this->assertFalse($user->can('view', $photo->content->contentElement));
        $this->assertFalse($photo->viewable());

        $this->assertEquals(0, Photo::getPhotos()->count());

        $role = Role::factory()->create();
        $user->addRole($role);
        $role->createPermission('update', $page);
        $user->refresh();
        $page->refresh();
        cache()->tags(['photos'])->flush();

        $this->assertTrue($user->can('view', $photo->content->contentElement));
        $this->assertEquals(0, Photo::getPhotos()->count());
        $this->assertFalse($photo->viewable());

        //dump('PHOTO: '.$photo->id);
        //dump('USER: '.$user->id);
        //dump('CE: '.get_class($photo->content->contentElement).' '.$photo->content->contentElement->id);
        $this->signIn($user);
        $this->assertTrue($user->canPerformAction('update', $page));
        $pages = $photo->content->contentElement->contentables->map(function ($contentable) {
            return $contentable->pageable;
        });

        $this->assertEquals(1, $pages->count());
        $this->assertTrue($pages->contains('id', $page->id));
        $this->assertTrue($user->can('update', $pages->first()));
        $this->assertTrue($user->can('view', $photo->content->contentElement));
        $this->assertEquals(1, Photo::getPhotos()->count());
        $this->assertTrue($photo->viewable());
    }

    public function test_a_content_element_with_view_permissions_prevents_photo_viewable()
    {
        $page = Page::factory()->create();
        $uses_versioning = collect(class_uses($page))->contains(function ($class_name) {
            return class_basename($class_name) === 'VersioningTrait';
        });

        $this->assertTrue($uses_versioning);

        $content_element = $this->createContentElement(PhotoBlock::factory()->withText(), $page);

        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([
            'file_upload_id' => $file_upload->id,
            'link' => $page->id.'#c-'.$content_element->uuid,
        ]);

        $photo_block = $content_element->content;
        $photo = (new Photo())->savePhoto($input, null, $photo_block);
        $this->assertInstanceOf(Photo::class, $photo);

        $role = Role::factory()->create();
        $role->createPermission('view', $content_element);

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

        $this->assertFalse($photo->viewable());

        $this->signIn($user);

        $this->assertFalse($photo->viewable());

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

        $this->assertTrue($user->can('view', $content_element));
        $this->assertTrue($photo->viewable());
    }

    public function test_a_photo_has_a_offset_class_attribute()
    {
        $photo = $this->createPhoto();
        $this->assertNotNull($photo->offset_class);
        $this->assertEquals('object-50-50', $photo->offset_class);

        $photo->offsetX = 20;
        $photo->offsetY = 80;
        $photo->save();
        $photo->refresh();
        $this->assertEquals('object-20-80', $photo->offset_class);

        $photo->offsetX = 32;
        $photo->offsetY = 68;
        $photo->save();
        $photo->refresh();
        $this->assertEquals('object-30-70', $photo->offset_class);
    }
}
