<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\WithFaker;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

use Tests\Browser\Components\Feedback;
use Tests\Browser\Components\Editor;
use Tests\Browser\Components\AddContentElement;
use Tests\Browser\Components\PhotoUpload;
use Tests\Browser\Components\ContentElements;
use Tests\Browser\Components\Autocomplete;
use Tests\Browser\Pages\Login;

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

class ContentElementsTest extends DuskTestCase
{
    use WithFaker;

    public function test_a_content_element_can_be_created()
    {
        $this->browse(function ($browser) {
            $page = Page::factory()->create();

            $browser->visit(new Login())
                ->loginAndEditPage($page)
                ->waitFor('@content-elements-editor')
                ->pause(300) // wait for transitions
                ->with('@add-content-element-last', function ($browser) {
                    $browser->click('@add-text-block');
                })
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Text Block Saved');
                });

            $content_element = ContentElement::all()->last();

            $this->assertInstanceOf(TextBlock::class, $content_element->content);

            $text_block_data = TextBlock::factory()->raw();

            $browser->with('@content-element-'.$content_element->id, function ($browser) use ($text_block_data, $content_element) {
                $browser->type('@header', Arr::get($text_block_data, 'header'))
                        ->waitFor('@body-editor-'.$content_element->id)
                        ->with(new Editor('body', $content_element->id), function ($browser) use ($text_block_data) {
                            $browser->typeInEditor(Arr::get($text_block_data, 'body'));
                        });
            })
            ->pause(1500) // so that the debounce triggers
            ->within(new Feedback(), function ($browser) {
                $browser->assertFeedbackContains('Text Block Saved');
            })
            ->refresh()
            ->click('@debug')
            ->whenAvailable('@content-element-'.$content_element->id, function ($browser) use ($text_block_data) {
                $browser->waitFor('@header')
                    ->assertInputValue('@header', Arr::get($text_block_data, 'header'))
                    ->assertVue('contentElement.content.body', '<p>'.Arr::get($text_block_data, 'body').'</p>');
            })
            ->click('@user-menu')
            ->pause(250)
            ->waitFor('@logout-button')
            ->click('@logout-button')
            ->within(new Feedback(), function ($browser) {
                $browser->assertFeedbackContains('Logout Complete');
            });
        });
    }

    public function test_a_content_element_can_be_inserted_between_two_others()
    {
        $this->browse(function ($browser) {
            $page = Page::factory()->create();

            $browser->visit(new Login())
                ->loginAndEditPage($page)
                ->waitFor('@content-elements-editor')
                ->with(new AddContentElement(), function ($browser) {
                    $browser->create('photo-block');
                });

            $content_element = ContentElement::all()->last();

            $browser->with(new AddContentElement(), function ($browser) {
                $browser->create('photo-block');
            });

            $content_element2 = ContentElement::all()->last();

            $browser->with(new AddContentElement(0), function ($browser) {
                $browser->create('photo-block');
            });

            $content_element3 = ContentElement::all()->last();

            $browser->pause(1000) // give the resort time to trigger and save
                ->whenAvailable('@content-element-'.$content_element3->id, function ($browser) {
                    $browser->assertVue('contentElement.pivot.sort_order', 2);
                })
            ->within('@content-element-'.$content_element->id, function ($browser) {
                $browser->assertVue('contentElement.pivot.sort_order', 1);
            })
            ->within('@content-element-'.$content_element2->id, function ($browser) {
                $browser->assertVue('contentElement.pivot.sort_order', 3);
            });
        });
    }

    public function test_a_content_element_can_be_expanded_on_a_page()
    {
        $this->browse(function ($browser) {
            $page = Page::factory()->create();
            $user = User::factory()->create();
            $user->createPermission('publish', $page);

            $content_element1 = $this->createContentElement(PhotoBlock::factory(), $page);
            $content_element2 = $this->createContentElement(PhotoBlock::factory(), $page);

            $browser->visit(new Login())
                ->loginAndEditPage($page, $user)
                ->waitFor('@content-elements-editor')
                ->assertPresent('@content-element-'.$content_element1->id)
                ->assertPresent('@content-element-'.$content_element2->id);

            $header = $this->faker->sentence(3);
            $header2 = $this->faker->sentence(3);
            $body = $this->faker->paragraph();
            $link = $this->faker->sentence(3).' >';

            // add the text for the second content element
            $browser->pause(500)
            ->with('@content-element-'.$content_element2->id, function ($browser) use ($header, $body, $content_element2) {
                $browser->click('@toggle-text')
                    ->waitFor('@header')
                    ->type('@header', $header)
                    ->within(new Editor('body', $content_element2->id), function ($browser) use ($body) {
                        $browser->typeInEditor($body);
                    })
                    ->click('@toggle-expandable');
            })
            ->within(new Feedback(), function ($browser) {
                $browser->assertFeedbackContains('Photo Block Saved');
            })
            ->with('@content-element-'.$content_element2->id, function ($browser) use ($header, $body) {
                $browser->assertSee('Expandable');
            });


            $browser->with('@content-element-'.$content_element1->id, function ($browser) use ($header2, $link, $content_element1) {
                $browser->click('@toggle-text')
                    ->waitFor('@header')
                    ->type('@header', $header2)
                    ->within(new Editor('body', $content_element1->id), function ($browser) use ($link) {
                        $browser->typeInEditor($link)
                            ->keys('@editor', ['{command}', 'a'])
                            ->click('@link-button');
                    });
            })
            ->with('@modal', function ($browser) use ($page, $content_element2) {
                $browser->with('@link-menu', function ($browser) use ($page, $content_element2) {
                    $browser->waitFor('@page-list-'.$page->id.'-toggle-content-elements')
                            ->pause(200)
                            ->click('@page-list-'.$page->id.'-toggle-content-elements')
                            ->waitFor('@content-element-'.$content_element2->id, 20)
                            ->click('@content-element-'.$content_element2->id);
                });
            })
            ->pause(1500)
            ->within(new Feedback(), function ($browser) {
                $browser->assertFeedbackContains('Photo Block Saved');
            });

            $browser->click('@publish-page')
                ->acceptDialog()
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Page Published');
                })
                ->clickAndWaitForReload('@editing-button')
                ->assertNotPresent('@page-editor')
                ->assertDontSee($body)
                ->assertSee($link)
                ->clickLink($link)
                ->pause(500) // for the expander
                ->assertSee($body);
        });
    }

    public function test_updating_a_content_element_broadcasts_the_update_to_other_editors()
    {
        $this->browse(function ($browser, $editor) {
            $page = Page::factory()->create();
            $user = User::factory()->create();
            $user2 = User::factory()->create();

            $browser->visit(new Login())
                    ->loginAndEditPage($page, $user)
                    ->waitFor('@page-editor');

            $editor->visit(new Login())
                   ->loginAndEditPage($page, $user2)
                    ->waitFor('@page-editor');


            $browser->waitFor('@add-content-element-last')
                    ->with(new AddContentElement(), function ($browser) {
                        $browser->create('photo-block');
                    })
                    ->pause(1500);

            $content_element = ContentElement::all()->last();

            $editor->waitFor('@content-element-'.$content_element->id);

            $browser->with(new PhotoUpload($content_element), function ($browser) {
                $browser->addPhoto();
            })->pause(2000);

            $photo = Photo::all()->last();

            $editor->waitFor('@form-photo-'.$photo->id);

            $header = $this->faker->words(3, true);
            $body = $this->faker->paragraph();

            $browser->with('@content-element-'.$content_element->id, function ($browser) use ($header, $body, $content_element) {
                $browser->click('@toggle-text')
                    ->waitFor('@header')
                    ->type('@header', $header)
                    ->within(new Editor('body', $content_element->id), function ($browser) use ($body) {
                        $browser->typeInEditor($body);
                    });
            })
            ->pause(1500);

            $editor->waitFor('@content-element-'.$content_element->id)
                    ->pause(1000)
                   ->with('@content-element-'.$content_element->id, function ($editor) use ($header, $body, $content_element) {
                       $editor->waitFor('@header')
                              ->assertValue('@header', $header)
                            ->assertVue('contentElement.content.body', '<p>'.$body.'</p>');
                   });
        });
    }

    public function test_hidden_content_cannot_show_up_in_search_results()
    {
        $this->browse(function ($browser, $search) {
            $page = Page::factory()->create();
            $user = User::factory()->create();
            $user->createPermission('publish', $page);

            $browser->visit(new Login())
                    ->loginAndEditPage($page, $user)
                    ->with(new AddContentElement(), function ($browser) {
                        $browser->create('photo-block');
                    })
                    ->pause(1500);

            $content_element = ContentElement::all()->last();
            $header = Str::random(12);
            $body = $this->faker->paragraph();

            $browser->with('@content-element-'.$content_element->id, function ($browser) use ($header, $body, $content_element) {
                $browser->click('@toggle-text')
                    ->waitFor('@header')
                    ->type('@header', $header)
                    ->within(new Editor('body', $content_element->id), function ($browser) use ($body) {
                        $browser->typeInEditor($body);
                    });
            })
                ->pause(1500)
                ->click('@publish-page')
                ->acceptDialog()
                ->pause(1500)
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Published');
                });


            $page->refresh();
            $slug = $this->getEditingValue($page, 'full_slug');
            $this->assertNotNull($slug);

            $search->visit($slug)
                   ->waitFor('@content-element-'.$content_element->id, 10)
                    ->assertSee($header)
                    ->visit('/search')
                    ->waitFor('@global-search')
                    ->with('@global-search', function ($search) use ($header, $page) {
                        $search->type('@search-input', $header)
                           ->waitFor('@paginator-search-count')
                           ->assertSeeIn('@paginator-search-count', '1')
                           ->waitFor('@search-result-page-'.$page->id, 20);
                    });

            $browser->with('@content-element-'.$content_element->id, function ($browser) {
                $browser->pause(500)
                        ->click('@toggle-unlisted');
            })
                ->pause(2000)
                ->refresh()
                ->waitFor('@publish-page', 10)
                ->click('@publish-page')
                ->acceptDialog();

            $search->refresh()
                    ->waitFor('@global-search')
                    ->with('@global-search', function ($search) use ($header, $page) {
                        $search->type('@search-input', $header)
                               ->waitFor('@paginator-search-count')
                               ->assertSeeIn('@paginator-search-count', '0')
                               ->assertMissing('@search-result-page-'.$page->id);
                    });
        });
    }

    public function test_a_content_element_can_be_visible_for_a_guest_only_or_a_role()
    {
        $this->browse(function ($browser, $browser2) {
            $page = Page::factory()->create();
            $user = User::factory()->create();
            $user->createPermission('publish', $page);

            $header = $this->faker->words(3, true);
            $body = $this->faker->paragraph();

            $browser->visit(new Login())
                    ->loginAndEditPage($page, $user)
                    ->within(new ContentElements(), function ($browser) use ($header, $body) {
                        $browser->createPhotoBlock($header, $body);
                    });

            $content_element = ContentElement::all()->last();

            $browser->with('@content-element-'.$content_element->id, function ($browser) {
                $browser->click('@toggle-guest');
            })
                ->pause(1500)
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Photo Block Saved');
                });

            $content_element->refresh();
            $contentable = $content_element->contentables()->first();

            $this->assertTrue($contentable->guest);
            $slug = $this->getEditingValue($page, 'full_slug');

            $browser->click('@publish-page')
                    ->acceptDialog()
                    ->clickAndWaitForReload('@editing-button')
                    ->pause(1000)
                    ->assertMissing('@content-element-'.$content_element->id)
                    ->click('@user-menu')
                    ->pause(500)
                    ->click('@logout-button')
                    ->waitForRoute('login')
                    ->visit($slug)
                    ->assertVisible('@content-element-'.$content_element->id);

            $user->addRole('admin');
            $browser->visit(new Login())
                    ->loginAndEditPage($page, $user)
                    ->waitFor('@page-editor')
                    ->waitFor('@content-element-'.$content_element->id)
                    ->with('@content-element-'.$content_element->id, function ($browser) {
                        $browser->click('@toggle-guest');
                    })
                    ->pause(1500)
                    ->within(new Feedback(), function ($browser) {
                        $browser->assertFeedbackContains('Photo Block Saved');
                    });

            $content_element = ContentElement::all()->last();
            $user2 = User::factory()->create();
            $role = Role::factory()->create();

            $browser->refresh()
                ->whenAvailable('@content-element-'.$content_element->id, function ($browser) use ($role, $content_element) {
                    $browser->waitFor('@show-permissions')
                            ->pause(500)
                            ->click('@show-permissions')
                            ->pause(500)
                            ->with('@content-element-permissions-'.$content_element->id, function ($browser) use ($role) {
                                $browser->waitFor('@select-view')
                                    ->click('@select-view')
                                    ->pause(500)
                                    ->with(new Autocomplete('permissions-add-role'), function ($browser) use ($role) {
                                        $browser->searchAndSelectSingle($role);
                                    });
                            });
                })
                    ->pause(500)
                    ->within(new Feedback(), function ($browser) {
                        $browser->assertFeedbackContains('Permissions Saved');
                    })
                    ->pause(1500)
                    ->click('@publish-page')
                    ->acceptDialog();

            $browser2->visit($slug)
                ->pause(500)
                ->assertMissing('@content-element-'.$content_element->id)
                ->visit(new Login())
                ->loginUser($user2)
                ->visit($slug)
                ->pause(500)
                ->assertMissing('@content-element-'.$content_element->id);

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

            $browser2->visit($slug)
                ->pause(500)
                ->assertVisible('@content-element-'.$content_element->id)
                ->assertSee($header)
                ->assertSee($body);
        });
    }

    public function test_a_content_element_on_two_pages_can_be_hidden_on_just_one_and_its_settings_stick()
    {
        $this->browse(function ($browser) {
            $page = Page::factory()->create();
            $page2 = Page::factory()->create();
            $user = User::factory()->create();
            $user->createPermission('update', $page);
            $user->createPermission('update', $page2);
            $user->createPermission('publish', $page);
            $user->createPermission('publish', $page2);
            $slug = $this->getEditingValue($page, 'full_slug');
            $slug2 = $this->getEditingValue($page2, 'full_slug');

            $browser->visit(new Login())
                    ->loginAndEditPage($page, $user)
                    ->with(new AddContentElement(), function ($browser) {
                        $browser->create('text-block');
                    })
                    ->pause(1500);

            $content_element = ContentElement::all()->last();
            $header = Str::random(12);
            $body = $this->faker->paragraph();

            $browser->with('@content-element-'.$content_element->id, function ($browser) use ($header, $body, $content_element) {
                $browser->waitFor('@header')
                    ->type('@header', $header)
                    ->within(new Editor('body', $content_element->id), function ($browser) use ($body) {
                        $browser->typeInEditor($body);
                    });
            })
                ->pause(1500)

                ->visit($slug2)
                ->waitFor('@page-editor')
                ->with(new AddContentElement(), function ($browser) {
                    $browser->create('text-block');
                })
                ->pause(1500)
                ->with(new AddContentElement(), function ($browser) use ($content_element) {
                    $browser->instance($content_element);
                })
                ->pause(1500)
                ->with('@content-element-'.$content_element->id, function ($browser) use ($header, $body, $content_element) {
                    $browser->click('@toggle-unlisted');
                })
                ->click('@debug')
                ->pause(1500);

            $content_element->refresh();
            $this->assertEquals(2, $content_element->contentables()->count());
            $this->assertEquals(2, $content_element->pages()->count());

            $this->assertEquals(1, $content_element->pages()->get()->first()->pivot->sort_order);
            $this->assertEquals(2, $content_element->pages()->get()->last()->pivot->sort_order);

            $this->assertEquals(0, $content_element->pages()->get()->first()->pivot->unlisted);
            $this->assertEquals(1, $content_element->pages()->get()->last()->pivot->unlisted);

            $header2 = $this->faker->words(3, true);

            $browser->click('@publish-page')
                ->acceptDialog()
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Page Published');
                })
                ->pause(2500) // so that the websocket update doesnt hammer the page
                ->visit($slug)
                ->whenAvailable('@page-editor', function ($browser) {
                    $browser->pause(1000)
                        ->assertMissing('@publish-page'); // both pages should be published
                })
                ->with('@content-element-'.$content_element->id, function ($browser) use ($header2, $body, $content_element) {
                    $browser->waitFor('@header')
                        ->type('@header', $header2);
                })
                //->pause(2500) // so that the websocket update doesnt hammer the page
                ->waitFor('@feedback')
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Text Block Saved');
                });

            $content_element->refresh();
            $new_content_element = ContentElement::all()->last();
            $this->assertEquals($content_element->uuid, $new_content_element->uuid);
            $this->assertEquals($header2, $new_content_element->content->header);

            $this->assertEquals(2, $content_element->contentables()->count());
            $this->assertEquals(2, $content_element->pages()->count());

            $this->assertEquals(1, $content_element->pages()->get()->first()->pivot->sort_order);
            $this->assertEquals(2, $content_element->pages()->get()->last()->pivot->sort_order);

            $this->assertEquals(0, $content_element->pages()->get()->first()->pivot->unlisted);
            $this->assertEquals(1, $content_element->pages()->get()->last()->pivot->unlisted);

            $this->assertEquals(2, $new_content_element->contentables()->count());
            $this->assertEquals(2, $new_content_element->pages()->count());

            $this->assertEquals(1, $new_content_element->pages()->get()->first()->pivot->sort_order);
            $this->assertEquals(2, $new_content_element->pages()->get()->last()->pivot->sort_order);

            $this->assertEquals(0, $new_content_element->pages()->get()->first()->pivot->unlisted);
            $this->assertEquals(1, $new_content_element->pages()->get()->last()->pivot->unlisted);

            $browser->refresh()
                ->waitFor('@publish-page')
                ->click('@publish-page')
                ->acceptDialog()
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Page Published');
                })
                ->pause(1500);

            $header3 = $this->faker->words(2, true);

            $browser->visit($slug2)
                ->waitFor('@page-editor')

                ->with('@content-element-'.$new_content_element->id, function ($browser) use ($header3, $new_content_element) {
                    $browser->waitFor('@header')
                        ->type('@header', $header3);
                })
                ->pause(1500)
                ->within(new Feedback(), function ($browser) {
                    $browser->assertFeedbackContains('Text Block Saved');
                })
                ->refresh()
                ->waitFor('@publish-page')
                ->visit($slug)
                ->waitFor('@content-element-'.ContentElement::all()->last()->id)
                ->pause(500);

            $third_content_element = ContentElement::all()->last();
            $this->assertEquals($content_element->uuid, $third_content_element->uuid);
            $this->assertEquals($header3, $third_content_element->content->header);

            $this->assertEquals(2, $third_content_element->contentables()->count());
            $this->assertEquals(2, $third_content_element->pages()->count());

            $this->assertEquals(1, $third_content_element->pages()->get()->first()->pivot->sort_order);
            $this->assertEquals(2, $third_content_element->pages()->get()->last()->pivot->sort_order);

            $this->assertEquals(0, $third_content_element->pages()->get()->first()->pivot->unlisted);
            $this->assertEquals(1, $third_content_element->pages()->get()->last()->pivot->unlisted);
        });
    }
}
