<?php

namespace Tests\Feature;

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

use App\Models\Page;
use App\Models\TextBlock;
use App\Models\ContentElement;
use App\Models\User;
use App\Models\Blog;
use App\Utilities\SearchResult;
use App\Models\Livestream;
use App\Models\Role;
use App\Models\PhotoBlock;
use App\Models\YoutubeVideo;
use App\Models\Quote;
use App\Models\Slideshow;
use App\Models\Tag;
use App\Models\Photo;
use App\Models\FileUpload;
use App\Models\Version;
use App\Models\PageSlug;

class SearchResultTest extends TestCase
{
    use WithFaker;

    public function test_unpublished_content_should_not_show_up_in_search_results()
    {
        $page = Page::factory()->create();
        $page2 = Page::factory()->create();
        $terms = 'TARGET-'.Str::random(8);
        $published_content = $this->createContentElement(TextBlock::factory(['body' => $terms]), $page);
        $page->publish();
        $unpublished_content = $this->createContentElement(TextBlock::factory(['body' => $terms]), $page2);

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $published_content->content->header,
             ])
             ->assertJsonMissing([
                 'title' => $unpublished_content->content->header,
             ]);

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

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

        $this->assertNotNull($unpublished_content->getPageVersion($page2));

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonFragment([
                 'title' => $unpublished_content->content->header,
             ]);
         */
    }

    public function test_the_lastest_version_of_a_content_element_is_the_result()
    {
        $page = Page::factory()->create();
        $term = Str::random(12);
        $body = $this->faker->paragraph;
        $content_element = $this->createContentElement(TextBlock::factory([
            'body' => '<p>'.$body.' '.$term.'</p>',
        ]), $page);
        $text_block = $content_element->content;
        $page->publish();
        $uuid = $content_element->uuid;

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

        $input = $content_element->toArray();
        $text_block_input = TextBlock::factory()->raw();
        $input['content']['header'] = Arr::get($text_block_input, 'header');
        $input['content']['body'] = '<p>'.Arr::get($text_block_input, 'body').' '.$term.'</p>';
        $input['content']['style'] = Arr::get($text_block_input, 'style');
        $input['pivot'] = $this->getContentableArray($page);
        /*
            'contentable_id' => $page->id,
            'contentable_type' => get_class($page),
            'sort_order' => 1,
            'unlisted' => false,
            'expandable' => false,
            'guest' => false,
        ];
         */

        $content = (new TextBlock());
        $content->header = Arr::get($input, 'content.header');
        $content->body = Arr::get($input, 'content.body');
        $content->style = Arr::get($input, 'content.style');

        $this->json('POST', route('content-elements.update', ['id' => $content_element->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Text Block Saved',
                'header' => $content->header,
                'body' => $content->body,
                'style' => $content->style,
             ]);

        $page->publish();

        $new_content_element = ContentElement::where('uuid', $uuid)->orderBy('id')->get()->last();
        $this->assertTrue($new_content_element->id > $content_element->id);
        $new_text_block = $new_content_element->content;

        $this->assertEquals($content->header, $new_text_block->header);
        $this->assertEquals($content->body, $new_text_block->body);
        $this->assertEquals($content->style, $new_text_block->style);

        $this->assertNotNull($content_element->getPageVersion($page)->published_at);
        $this->assertNotNull($new_content_element->getPageVersion($page)->published_at);

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $new_text_block->header,
             ])
             ->assertJsonMissing([
                 'title' => $text_block->header,
             ]);
    }

    public function test_a_page_can_be_returned_as_a_search_result()
    {
        $term = Str::random(8);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->name = $term;
        $version->title = null;
        $version->save();
        $content_element = $this->createContentElement(TextBlock::factory(), $page);
        $page->publish();

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $page->version->name,
             ]);
    }

    public function test_an_unpublished_page_can_be_a_result_if_there_are_permissions()
    {
        $term = Str::random(8);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->name = $term;
        $version->title = null;
        $version->save();
        $content_element = $this->createContentElement(TextBlock::factory(), $page);

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $term,
             ]);

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

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $term,
             ]);

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

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $term,
             ]);
    }

    public function test_an_unpublished_page_should_only_show_its_published_preview()
    {
        $term = Str::random(8);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->name = $term;
        $version->title = null;
        $version->save();
        $published_content_element = $this->createContentElement(TextBlock::factory(), $page);
        $page->publish();

        $published_preview = $published_content_element->content->getSearchResultPreview($term);
        $this->assertNotNull($published_preview);

        $this->signInAdmin();

        $input = $published_content_element->toArray();
        $text_block_input = TextBlock::factory()->raw();
        $input['content']['header'] = Arr::get($text_block_input, 'header');
        $input['content']['body'] = '<p>'.Arr::get($text_block_input, 'body').'</p>';
        $input['content']['style'] = Arr::get($text_block_input, 'style');
        $input['pivot'] = $this->getContentableArray($page);
        /*
            'contentable_id' => $page->id,
            'contentable_type' => get_class($page),
            'sort_order' => 1,
            'unlisted' => false,
            'expandable' => false,
            'guest' => false,
        ];
         */

        $content = (new TextBlock());
        $content->header = Arr::get($input, 'content.header');
        $content->body = Arr::get($input, 'content.body');
        $content->style = Arr::get($input, 'content.style');

        $this->json('POST', route('content-elements.update', ['id' => $published_content_element->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Text Block Saved',
                'header' => $content->header,
                'body' => $content->body,
                'style' => $content->style,
             ]);

        $unpublished_content_element = ContentElement::where('uuid', $published_content_element->uuid)->orderBy('id')->get()->last();
        $unpublished_preview = $unpublished_content_element->content->getSearchResultPreview($term);

        $this->assertNotEquals($published_preview, $unpublished_preview);

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

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'preview' => $unpublished_preview,
             ])
             ->assertJsonFragment([
                'preview' => $published_preview,
             ]);

        $this->signInAdmin();
    }

    public function test_a_page_is_ranked_above_a_content_element()
    {
        $term = Str::random(8);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->name = $term;
        $version->title = null;
        $version->save();

        $page2 = Page::factory()->create();
        $header_content_element = $this->createContentElement(TextBlock::factory([
            'header' => 'header '.$term,
        ]), $page2);

        $page3 = Page::factory()->create();
        $body_content_element = $this->createContentElement(TextBlock::factory([
            'body' => $this->faker->sentence.'. '.$term.' '.$this->faker->sentence,
        ]), $page3);

        $page->publish();
        $page2->publish();
        $page3->publish();

        $this->withoutExceptionHandling();

        $results = SearchResult::search($term, collect(['pages']));

        $this->assertEquals(3, $results->count());
        $this->assertNotNull($results[0]->rank);
        $this->assertNotNull($results[1]->rank);
        $this->assertNotNull($results[2]->rank);

        $this->assertTrue($results[0]->rank > $results[1]->rank);
        $this->assertTrue($results[1]->rank > $results[2]->rank);

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $page->version->name,
                'title' => 'header '.$term,
                'title' => $body_content_element->content->header,
             ]);
    }

    public function test_a_text_block_returns_a_link_to_its_content_element()
    {
        $page = Page::factory()->create();

        $header = $this->faker->sentence;
        $term = Str::random(12);

        $content_element = $this->createContentElement(TextBlock::factory([
            'header' => $header,
            'body' => '<p>'.$this->faker->paragraph.' '.$term.'</p>',
        ]), $page);

        $text_block = $content_element->content;

        $page->publish();

        $results = SearchResult::search($term);

        $this->assertEquals(1, $results->count());

        $url = $results[0]->url;
        $this->assertNotNull($url);
        $this->assertNotNull($content_element->uuid);
        $this->assertTrue(Str::contains($url, (string) $content_element->anchor));
    }

    public function test_a_blog_content_element_can_be_searched_for()
    {
        $blog = Blog::factory()->create();

        $header = $this->faker->sentence;
        $term = Str::random(12);

        $content_element = $this->createContentElement(TextBlock::factory([
            'header' => $header,
            'body' => '<p>'.$this->faker->paragraph.' '.$term.'</p>',
        ]), $blog);

        $blog->publish();

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);
    }

    public function test_a_livestream_can_be_searched_for()
    {
        $term = Str::random(12);
        $livestream = Livestream::factory()->create([
            'name' => $term.' LiveStream',
        ]);

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['livestreams' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $livestream->name,
             ]);
    }

    public function test_only_livestreams_with_permission_can_be_viewed_in_search_results()
    {
        $role = Role::factory()->create();
        $role_user = User::factory()->create();
        $role_user->addRole($role);
        $role_user->refresh();

        $term = Str::random(12);
        $livestream = Livestream::factory()->create([
            'name' => $term.' LiveStream',
        ]);

        $livestream->createPermission('view', $role);

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['livestreams' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $livestream->name,
             ]);

        $this->signIn($role_user);

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['livestreams' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $livestream->name,
             ]);
    }

    public function test_a_photo_block_can_be_searched_for()
    {
        $page = Page::factory()->create();
        $term1 = Str::random(12);
        $term2 = Str::random(12);
        $content_element_body = $this->createContentElement(PhotoBlock::factory(['body' => $this->faker->sentence.'. '.$term1, 'header' => Str::random(12)]), $page);
        $content_element_header = $this->createContentElement(PhotoBlock::factory(['header' => $term2]), $page);
        $content_element2 = $this->createContentElement(TextBlock::factory(['body' => Str::random(12)]), $page);
        $page->publish();

        $this->get($page->full_slug)
            ->assertSuccessful();

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term1])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_body->content->header,
             ])
             ->assertJsonMissing([
                'title' => $content_element2->content->header,
             ]);

        $this->json('POST', route('search'), ['terms' => $term2])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_header->content->header,
             ])
             ->assertJsonMissing([
                'title' => $content_element2->content->header,
             ]);
    }

    public function test_a_youtube_video_can_be_searched_for()
    {
        $page = Page::factory()->create();
        $content_element_title = $this->createContentElement(YoutubeVideo::factory([
            'title' => Str::random(12),
        ]), $page);

        $content_element_header = $this->createContentElement(YoutubeVideo::factory([
            'title' => null,
            'header' => Str::random(12),
        ]), $page);

        $body_search = Str::random(12);
        $content_element_body = $this->createContentElement(YoutubeVideo::factory([
            'title' => null,
            'header' => Str::random(12),
            'body' => $this->faker->sentence.' '.$body_search,
        ]), $page);

        $page->publish();

        $this->json('POST', route('search'), ['terms' => $content_element_title->content->title])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_title->content->title,
             ]);

        $this->json('POST', route('search'), ['terms' => $content_element_header->content->header])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_header->content->header,
             ]);

        $this->json('POST', route('search'), ['terms' => $body_search])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_body->content->header,
             ]);
    }


    public function test_a_quote_can_be_searched_for()
    {
        $page = Page::factory()->create();

        $term1 = Str::random(12);
        $term2 = Str::random(12);
        $term3 = Str::random(12);

        $content_element_body = $this->createContentElement(Quote::factory([
            'body' => $term1,
        ]), $page);

        $content_element_name = $this->createContentElement(Quote::factory([
            'author_name' => $term2,
        ]), $page);

        $content_element_details = $this->createContentElement(Quote::factory([
            'author_name' => $term3,
        ]), $page);

        $page->publish();

        $this->json('POST', route('search'), ['terms' => $term1])
             ->assertSuccessful()
             ->assertJsonFragment([
                'preview' => $content_element_body->content->getSearchResultPreview([$term1]),
             ]);

        $this->json('POST', route('search'), ['terms' => $term2])
             ->assertSuccessful()
             ->assertJsonFragment([
                'preview' => $content_element_name->content->getSearchResultPreview([$term2]),
             ]);

        $this->json('POST', route('search'), ['terms' => $term3])
             ->assertSuccessful()
             ->assertJsonFragment([
                'preview' => $content_element_details->content->getSearchResultPreview([$term3]),
             ]);
    }


    public function test_a_slideshow_can_be_searched_for()
    {
        $page = Page::factory()->create();

        $term1 = Str::random(12);
        $term2 = Str::random(12);

        $content_element_body = $this->createContentElement(Slideshow::factory([
            'body' => $this->faker->sentence.' '.$term1,
        ]), $page);

        $content_element_header = $this->createContentElement(Slideshow::factory([
            'header' => $term2,
        ]), $page);

        $page->publish();

        $this->json('POST', route('search'), ['terms' => $term1])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_body->content->header,
             ]);

        $this->json('POST', route('search'), ['terms' => $term2])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element_header->content->header,
             ]);
    }


    public function test_same_page_search_results_are_grouped_together()
    {
        $page = Page::factory()->create();
        $term = Str::random(12);

        $content_element1 = $this->createContentElement(TextBlock::factory([
            'body' => $this->faker->paragraph.' '.$term,
        ]), $page);

        $content_element2 = $this->createContentElement(TextBlock::factory([
            'header' => $term,
        ]), $page);

        $content_element3 = $this->createContentElement(PhotoBlock::factory([
            'body' => $this->faker->paragraph.' '.$term,
        ]), $page);

        $page->publish();

        $results = SearchResult::search($term);

        $this->assertEquals(1, $results->count());
    }

    public function test_a_bunch_of_resuts_can_be_paginated()
    {
        $term = Str::random(12);
        $count = 0;
        $content_elements = collect();
        while ($count < 20) {
            $page = Page::factory()->create();
            $content_element = $this->createContentElement(TextBlock::factory(['header' => $term]), $page);
            $page->publish();
            $content_elements->push($content_element);
            $count++;
        }

        $results = SearchResult::search($term);

        $this->assertEquals($content_elements->count(), $results->count());

        $this->json('POST', route('search'), ['terms' => $term])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_elements->first()->content->header,
             ]);
    }

    public function test_tagged_pages_can_be_found_in_searching()
    {
        $term = Str::random(12);
        $tag = Tag::factory()->create([
            'name' => $term,
        ]);

        $page = Page::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $page);

        $page->addTag($tag);

        $page->publish();

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $page->version->title,
             ]);
    }

    public function test_tagged_content_elements_can_be_found_in_searching()
    {
        $term = Str::random(12);
        $tag = Tag::factory()->create([
            'name' => $term,
        ]);

        $page = Page::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $page);

        $content_element->addTag($tag);

        $page->publish();

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);
    }

    public function test_a_tagged_livestream_can_be_searched_for()
    {
        $term = Str::random(12);
        $tag = Tag::factory()->create([
            'name' => $term,
        ]);

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

        $livestream->addTag($tag);

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['livestreams' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $livestream->name,
             ]);
    }

    public function test_a_photo_can_be_searched_for()
    {
        $page = Page::factory()->create();
        $name = Str::random(12);
        $description = Str::random(12);
        $alt = Str::random(12);

        $content_element = $this->createContentElement(PhotoBlock::factory()->withText()->has(Photo::factory([
            'name' => $name,
            'description' => $description,
            'alt' => $alt,
        ])->for(FileUpload::factory()->jpg(), 'fileUpload')), $page);

        $this->assertNotNull($content_element->content->header);

        $photo_block = $content_element->content;
        $photo = $photo_block->photos->first();
        $this->assertInstanceOf(Photo::class, $photo);

        $page->publish();

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $name, 'filters' => ['photos' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);
    }

    public function test_an_unpublished_photo_is_not_a_search_result()
    {
        $page = Page::factory()->create();
        $name = Str::random(12);
        $description = Str::random(12);
        $alt = Str::random(12);

        $content_element = $this->createContentElement(PhotoBlock::factory()->withText()->has(Photo::factory([
            'name' => $name,
            'description' => $description,
            'alt' => $alt,
        ])->for(FileUpload::factory()->jpg(), 'fileUpload')), $page);

        $this->assertNotNull($content_element->content->header);

        $photo_block = $content_element->content;
        $photo = $photo_block->photos->first();
        $this->assertInstanceOf(Photo::class, $photo);

        $this->withoutExceptionHandling();
        $this->json('POST', route('search'), ['terms' => $name, 'filters' => ['photos' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $content_element->content->header,
             ]);

        $page->publish();

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

        $this->json('POST', route('search'), ['terms' => $name, 'filters' => ['photos' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);
    }

    public function test_a_search_excluded_pages_photo_is_not_a_search_result()
    {
        $page = Page::factory()->create();
        $name = Str::random(12);
        $description = Str::random(12);
        $alt = Str::random(12);

        $content_element = $this->createContentElement(PhotoBlock::factory()->withText()->has(Photo::factory([
            'name' => $name,
            'description' => $description,
            'alt' => $alt,
        ])->for(FileUpload::factory()->jpg(), 'fileUpload')), $page);

        $this->assertNotNull($content_element->content->header);

        $photo_block = $content_element->content;
        $photo = $photo_block->photos->first();
        $this->assertInstanceOf(Photo::class, $photo);
        $page->publish();

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

        $this->json('POST', route('search'), ['terms' => $name, 'filters' => ['photos' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);

        $version = $page->getDraftVersion();
        $version->search_exclude = 1;
        $version->save();
        $page->refresh();
        $page->publish();
        $page->refresh();
        $this->assertEquals($version->id, $page->version->id);
        $this->assertTrue($page->version->search_exclude);

        $this->json('POST', route('search'), ['terms' => $name, 'filters' => ['photos' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $content_element->content->header,
             ]);
    }

    public function test_a_photo_can_be_returned_with_a_page_search_result()
    {
        $term = Str::random(12);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->title = $term;
        $version->save();

        $content_element2 = $this->createContentElement(PhotoBlock::factory()->withText()->has(Photo::factory()->for(FileUpload::factory()->jpg(), 'fileUpload')), $page);
        $content_element = $this->createContentElement(PhotoBlock::factory()->withText()->has(Photo::factory(['name' => $term])->for(FileUpload::factory()->jpg(), 'fileUpload')), $page);
        $photo_block = $content_element->content;
        $photo = $photo_block->photos->first();
        $this->assertInstanceOf(Photo::class, $photo);
        $page->publish();

        $result = SearchResult::search($term, collect(['pages']))->first();
        $this->assertInstanceOf(SearchResult::class, $result);
        $this->assertNotNull($result->photo);

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'large' => $photo->large,
             ]);
    }

    public function test_a_photo_can_be_returned_with_a_content_element_search_result()
    {
        $term = Str::random(12);
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(PhotoBlock::factory(['header' => $term])->has(Photo::factory()->for(FileUpload::factory()->jpg(), 'fileUpload')), $page);
        $photo_block = $content_element->content;
        $photo = $photo_block->photos->first();
        $this->assertInstanceOf(Photo::class, $photo);
        $page->publish();

        $result = SearchResult::search($term)->first();

        $this->assertInstanceOf(SearchResult::class, $result);

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

        $this->json('POST', route('search'), ['terms' => $term])
             ->assertSuccessful()
             ->assertJsonFragment([
                'large' => $photo->large,
             ]);
    }

    public function test_blogs_pages_and_livestreams_can_be_filtered_during_search()
    {
        $term = Str::random(12);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->name = $term;
        $version->title = Str::random(12);
        $version->save();
        $content_element1 = $this->createContentElement(TextBlock::factory(), $page);
        $page->publish();

        $blog = Blog::factory()->create();
        $version = $blog->versions->first();
        $version->name = $term;
        $version->title = Str::random(12);
        $version->save();

        $content_element2 = $this->createContentElement(TextBlock::factory(), $blog);
        $blog->publish();

        $livestream = Livestream::factory()->create([
            'name' => $term.' LiveStream',
        ]);

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true, 'blogs' => true, 'livestreams' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $page->version->title,
                'title' => $blog->version->title,
                'title' => $livestream->name,
             ]);

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

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $page->version->title,
             ])
             ->assertJsonMissing([
                'title' => $blog->version->title,
                'title' => $livestream->name,
             ]);

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

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['blogs' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $blog->version->title,
             ])
             ->assertJsonMissing([
                'title' => $page->version->title,
                'title' => $livestream->name,
             ]);

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

        $this->json('POST', route('search'), ['terms' => $term, 'filters' => ['livestreams' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $livestream->name,
             ])
             ->assertJsonMissing([
                'title' => $page->version->title,
                'title' => $blog->version->title,
             ]);
    }

    public function test_a_page_title_has_a_higher_search_ranking_than_a_blog_title()
    {
        $term = Str::random(12);
        $page = Page::factory()->create();
        $version = $page->versions->first();
        $version->name = $term;
        $version->title = Str::random(12);
        $version->save();

        $content_element1 = $this->createContentElement(TextBlock::factory(), $page);
        $page->publish();

        $blog = Blog::factory()->create();
        $version = $blog->versions->first();
        $version->name = $term;
        $version->title = Str::random(12);
        $version->save();

        $content_element2 = $this->createContentElement(TextBlock::factory(), $blog);
        $blog->publish();

        $results = SearchResult::search($term, collect(['pages', 'blogs']));

        $this->assertEquals(2, $results->count());

        $first_result = $results->first();
        $last_result = $results->last();

        $this->assertEquals($page->version->title, $first_result->title);
        $this->assertTrue($first_result->rank > $last_result->rank);
    }

    public function test_a_footer_image_doesnt_break_the_search_results()
    {
        $page = Page::factory()->create();
        $term = Str::random(12);

        Storage::fake();
        $fg_file_name = $term.'.jpg';
        $fg_file = UploadedFile::fake()->image($fg_file_name);
        $fg_file_upload = (new FileUpload())->saveFile($fg_file, 'photos', true);

        $input = $page->toArray();
        $input['version'] = $page->versions->first()->toArray();

        $fg_photo_input = Photo::factory()->raw([
            'name' => $term,
        ]);
        $fg_photo_input['file_upload_id'] = $fg_file_upload->id;

        $input['version']['footer_fg_photo'] = $fg_photo_input;

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

        $this->withoutExceptionHandling();
        $this->postJson(route('pages.update', ['id' => $page->id]), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => Arr::get($input, 'version.name').' Saved',
                'full_slug' => $page->refresh()->full_slug,
            ]);

        $page->publish();
        $page->refresh();

        $this->assertNotNull($page->footerFgPhoto);

        $results = Photo::searchResults([$term]);

        $this->assertNotNull($results);
        $this->assertEquals(0, $results->count());

        $this->json('POST', route('search'), ['terms' => $term])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $page->version->title,
             ]);
    }

    public function test_search_results_are_filtered_by_content_page_permissions()
    {
        $terms = 'TARGET-'.Str::random(8);
        $content_element = $this->createContentElement(TextBlock::factory(['body' => $terms]));
        $page = $content_element->pages->first();

        $this->assertInstanceOf(Page::class, $page);

        $page->publish();

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);

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

        $page->createPermission('view', $role);

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

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $content_element->content->header,
             ]);

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

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

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $content_element->content->header,
             ]);

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

        $user->refresh();

        $this->signIn($user);

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $content_element->content->header,
             ]);
    }

    public function test_search_results_are_filtered_by_page_permissions()
    {
        $terms = 'TARGET-'.Str::random(8);
        $page = Page::factory()->create();
        $version = $page->versions()->first();
        $version->name = $terms;
        $version->save();

        $content_element = $this->createContentElement(TextBlock::factory(), $page);
        $page->publish();

        $title = $page->version->title;
        $this->assertNotNull($title);

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);

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

        $page->createPermission('view', $role);

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

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $title,
             ]);

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

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $title,
             ]);

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

        $user->refresh();

        $this->signIn($user);

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);
    }

    public function test_a_page_can_be_excluded_from_search_results()
    {
        $page = Page::factory()->create();
        $version = $page->versions->first();

        $page->publish();

        $terms = $version->name;
        $title = $version->title;
        $this->assertNotNull($terms);
        $this->assertNotNull($title);
        $this->assertEquals(0, $version->search_exclude);

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);

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

        $input['version'] = Version::factory()->page()->raw([
            'search_exclude' => true,
        ]);

        $this->postJson(route('pages.update', ['id' => $page->id]), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => Arr::get($input, 'version.name').' Saved',
            ]);

        $page->refresh();
        $version = $page->versions->last();

        $this->assertEquals(1, $version->search_exclude);
        $new_title = $version->title;

        $page->publish();

        $this->disableEditing();
        auth()->logout();

        $this->assertFalse(editing());

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $new_title,
                'title' => $title,
             ]);

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

        $draft_version = $page->getDraftVersion();
        $this->assertEquals($version->search_exclude, $draft_version->search_exclude);
    }

    public function test_a_pages_content_elements_can_be_excluded_from_search_results()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory([
            'header' => Str::random(12),
        ]), $page);
        $version = $page->versions->first();
        $page->publish();

        $terms = Str::substr($content_element->content->header, 0, 5);
        $title = $content_element->getSearchResultTitle();
        $this->assertNotNull($terms);
        $this->assertNotNull($title);
        $this->assertEquals(0, $version->search_exclude);

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);

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

        $input['version'] = Version::factory()->page()->raw([
            'search_exclude' => true,
        ]);

        $this->postJson(route('pages.update', ['id' => $page->id]), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => Arr::get($input, 'version.name').' Saved',
            ]);

        $page->refresh();
        $version = $page->versions->last();

        $this->assertEquals(1, $version->search_exclude);

        $page->publish();

        $this->disableEditing();
        auth()->logout();

        $this->assertFalse(editing());

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful()
             ->assertJsonMissing([
                'title' => $title,
             ]);

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

        $draft_version = $page->getDraftVersion();
        $this->assertEquals($version->search_exclude, $draft_version->search_exclude);
    }

    public function test_a_page_can_be_searched_by_its_page_slugs()
    {
        $page = Page::factory()->create();

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

        $page->publish();
        $slug = Str::random();

        $this->assertNotNull($page->full_type);

        $input = [
            'slug' => $slug,
            'pageable_id' => $page->id,
            'pageable_type' => $page->full_type,
            'root' => false,
        ];

        $this->json('POST', route('page-slugs.store'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Alias Saved',
             ]);

        $page_slug = PageSlug::all()->last();
        $this->assertEquals(Arr::get($input, 'slug'), $page_slug->slug);
        $this->assertEquals($page->id, $page_slug->pageable->id);
        $this->assertEquals(get_class($page), get_class($page_slug->pageable));

        $terms = $slug;
        $title = $page->versions()->get()->last()->title;
        $this->assertNotNull($title);

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);
    }

    public function test_a_page_can_be_searched_for_by_its_meta_description()
    {
        $page = Page::factory()->create();
        $version = $page->versions()->first();

        $terms = Str::random();
        $title = $version->title;
        $description = $this->faker->sentence.' '.$terms;
        $version->description = $description;
        $version->save();

        $page->publish();

        $this->json('POST', route('search'), ['terms' => $terms, 'filters' => ['pages' => true]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);
    }

    public function test_searching_for_a_content_element_that_has_had_its_page_deleted_doesnt_break_the_search()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $page);
        $terms = Str::substr($content_element->content->header, 0, 5);
        $this->assertNotNull($terms);

        $page->publish();
        $page->delete();

        $this->json('POST', route('search'), ['terms' => $terms])
             ->assertSuccessful();
    }

    public function test_unlisted_content_elements_dont_show_in_search_results()
    {
        $page = Page::factory()->create();
        $page2 = Page::factory()->create();
        $header = Str::random(12);
        $content_element1 = $this->createContentElement(TextBlock::factory([
            'header' => $header,
        ]), $page);

        $content_element2 = $this->createContentElement(TextBlock::factory([
            'header' => $header,
        ]), $page2);

        $contentable1 = $content_element1->contentables()->first();
        $contentable2 = $content_element2->contentables()->first();

        $page->publish();
        $page->refresh();
        $page2->publish();
        $page2->refresh();

        $this->json('POST', route('search'), ['terms' => $header])
             ->assertSuccessful()
            ->assertJsonFragment([
                'title' => $header,
                'id' => $page->id,
                'id' => $page2->id,
            ]);

        $contentable2->unlisted = true;
        $contentable2->save();

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

        $input = $content_element2->toArray();
        $input['pivot'] = $this->getContentableArray($page2);
        $input['pivot']['unlisted'] = 1;

        $this->json('POST', route('content-elements.update', ['id' => $content_element2->id]), $input)
            ->assertSuccessful();

        /*
        $this->json('POST', route('search'), ['terms' => $header])
             ->assertSuccessful()
            ->assertJsonFragment([
                'title' => $header,
                'id' => $page->id,
                'id' => $page2->id,
            ]);
         */

        $this->json('POST', route('pages.publish', ['id' => $page2->id]))
            ->assertSuccessful();

        $page2->refresh();
        $content_element2->refresh();

        /*
        $this->json('POST', route('search'), ['terms' => $header])
             ->assertSuccessful()
            ->assertJsonFragment([
                'title' => $header,
                'id' => $page->id,
            ])
            ->assertJsonMissing([
                'id' => $page2->id,
            ]);
         */

        $this->disableEditing();
        auth()->logout();

        $this->json('POST', route('search'), ['terms' => $header])
             ->assertSuccessful()
            ->assertJsonFragment([
                'title' => $header,
                'id' => $page->id,
            ])
            ->assertJsonMissing([
                'id' => $page2->id,
            ]);
    }
}
