<?php

namespace Tests\Unit;

use Illuminate\Support\Arr;

use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\UploadedFile;

use App\Events\PageDraftCreated;
use App\Events\BlogDraftCreated;
use App\Events\AnnouncementDraftCreated;
use App\Events\CourseDraftCreated;

use App\Models\Version;
use App\Models\Page;
use App\Models\User;
use App\Models\ContentElement;
use App\Models\TextBlock;
use App\Models\FileUpload;
use App\Models\Photo;

trait VersioningTestTrait
{
    abstract protected function getModel();
    abstract protected function getClassname();

    public function test_a_versionable_has_a_version_attribute()
    {
        $this->signInAdmin();
        $this->enableEditing();
        $page = $this->getModel();
        $draft_version = $page->getDraftVersion();
        $this->assertNotNull($page->version);
        $this->assertEquals($draft_version->id, $page->version->id);

        $this->assertNotNull($page->version);
        $this->assertInstanceOf(Version::class, $page->version);
        $this->assertEquals($draft_version->id, $page->version->id);
    }

    public function test_a_versionable_can_get_its_draft_version()
    {
        $page = $this->getModel();

        $this->assertInstanceOf(get_class($this->getModel()), $page);
        $draft_version = $page->getDraftVersion();
        $this->assertInstanceOf(Version::class, $draft_version);
    }

    public function test_a_versionable_has_a_published_version()
    {
        $page = $this->getModel();
        $page->publish();
        $page->refresh();
        $this->assertNotNull($page->published_version_id);
        $this->assertNotNull($page->publishedVersion);
        $this->assertInstanceOf(Version::class, $page->publishedVersion);
    }

    public function test_a_versionable_can_be_published()
    {
        $page = $this->getModel();
        $page->publish();
        $this->assertNotNull($page->published_version_id);
        $this->assertNotNull($page->publishedVersion);
        $this->assertInstanceOf(Version::class, $page->publishedVersion);
        $this->assertNotNull($page->publishedVersion->published_at);
        $this->assertNotNull($page->published_at);
    }

    public function test_a_versionable_has_many_versions()
    {
        $page = $this->getModel();
        $version = Version::factory()->create([
            'versionable_type' => get_class($page),
            'versionable_id' => $page->id,
        ]);
        $version->versionable_id = $page->id;
        $version->save();
        $page->refresh();
        $this->assertTrue($page->versions()->get()->contains('id', $version->id));
    }

    public function test_if_a_versionable_doesnt_have_a_draft_version_one_is_created()
    {
        $page = $this->getModel();
        $this->assertInstanceOf(Version::class, $page->getDraftVersion());
    }

    public function test_a_versionable_has_a_draft_version_id_attribute()
    {
        $page = $this->getModel();
        $this->assertNotNull($page->draft_version_id);
        $this->assertEquals($page->getDraftVersion()->id, $page->draft_version_id);
    }

    public function test_a_versionable_can_be_published_in_the_future()
    {
        $this->signInAdmin();
        $this->enableEditing();
        $page = $this->getModel();
        $version = $page->versions->first();
        $version->publish_at = now()->addMinutes(1);
        $version->save();

        $this->assertInstanceOf(Version::class, $page->getDraftVersion());
        $this->assertNull($page->published_at);

        Page::publishScheduledContent();
        $page->refresh();
        $this->assertNull($page->published_at);

        $version = $page->version;
        $version->publish_at = now()->subMinutes(1);
        $version->save();
        $page->refresh();
        cache()->tags([cache_name($page)])->flush();
        $this->assertTrue($page->version->publish_at->isPast());

        Artisan::call('brentwood:publish-scheduled-content');
        $page->refresh();
        cache()->tags([cache_name($page)])->flush();
        $this->assertNotNull($page->version->published_at);
        $this->assertNotNull($page->version->publisher);
        $this->assertEquals(auth()->user()->id, $page->version->publisher->id);
        //$this->assertNull($page->version->publish_at);
    }

    public function test_creating_a_new_version_broadcasts_an_event()
    {
        $page = $this->getModel();
        $page->publish();

        Event::fake();

        $page->getDraftVersion();

        if ($this->getClassname() === 'page') {
            Event::assertDispatched(function (PageDraftCreated $event) use ($page) {
                return $event->{$this->getClassname()}->id === $page->id;
            });
        } elseif ($this->getClassname() === 'blog') {
            Event::assertDispatched(function (BlogDraftCreated $event) use ($page) {
                return $event->{$this->getClassname()}->id === $page->id;
            });
        } elseif ($this->getClassname() === 'announcement') {
            Event::assertDispatched(function (AnnouncementDraftCreated $event) use ($page) {
                return $event->{$this->getClassname()}->id === $page->id;
            });
        } elseif ($this->getClassname() === 'course') {
            Event::assertDispatched(function (CourseDraftCreated $event) use ($page) {
                return $event->{$this->getClassname()}->id === $page->id;
            });
        }
    }

    public function test_a_versionable_has_a_can_be_published_attribute()
    {
        $content_element = $this->createContentElement(TextBlock::factory(), $this->getModel());
        $page = $content_element->{Str::plural($this->getClassname())}()->first();
        //$content_element->version_id = $page->draft_version_id;

        $page->refresh();

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

        if ($page->type === 'blog' || $page->type === 'announcement') {
            $user->addRole(Str::plural($this->getClassname()).'-publisher');
        } else {
            $user->createPermission('publish', $page);
        }
        $user->refresh();
        $page->refresh();

        $this->assertTrue($page->can_be_published);
        $page->publish();
        $page->refresh();
        $this->assertFalse($page->can_be_published);

        $content_element = ContentElement::factory()->for(TextBlock::factory(), 'content')->create();

        $content_element->{Str::plural($this->getClassname())}()->detach();
        $content_element->{Str::plural($this->getClassname())}()->attach($page, ['sort_order' => 1, 'unlisted' => true, 'randomize' => false, 'version_id' => $page->draft_version_id]);

        $content_element->save();

        $page->refresh();
        cache()->tags([cache_name($page)])->flush();
        $this->assertTrue($page->can_be_published);
    }

    public function test_a_versionable_can_save_its_version_info()
    {
        $page = $this->getModel();
        $page->publish();

        $input['version'] = Version::factory()->page()->slug()->randomize()->raw();

        $page->saveVersion($input);
        $page->refresh();
        $draft_version = $page->getDraftVersion();

        $this->assertInstanceOf(Version::class, $draft_version);

        $this->assertNotNull($draft_version->name);
        $this->assertNotNull($draft_version->title);
        $this->assertNotNull($draft_version->description);
        $this->assertNotNull($draft_version->slug);
        $this->assertNotNull($draft_version->sort_order);
        $this->assertNotNull($draft_version->unlisted);
        $this->assertNotNull($draft_version->show_sub_menu);
        $this->assertNotNull($draft_version->signed_url);
        $this->assertNotNull($draft_version->sibling_nav);
        $this->assertNotNull($draft_version->randomize);

        $this->assertEquals(Arr::get($input, 'version.name'), $draft_version->name);
        $this->assertEquals(Arr::get($input, 'version.title'), $draft_version->title);
        $this->assertEquals(Arr::get($input, 'version.description'), $draft_version->description);
        $this->assertEquals(Arr::get($input, 'version.slug'), $draft_version->slug);
        $this->assertEquals(Arr::get($input, 'version.parent_page_id'), $draft_version->parent_page_id);
        $this->assertEquals(Arr::get($input, 'version.theme'), $draft_version->theme);
        $this->assertEquals(Arr::get($input, 'version.sort_order'), $draft_version->sort_order);
        $this->assertEquals(Arr::get($input, 'version.unlisted'), $draft_version->unlisted);
        $this->assertEquals(Arr::get($input, 'version.show_sub_menu'), $draft_version->show_sub_menu);
        $this->assertEquals(Arr::get($input, 'version.footer_color'), $draft_version->footer_color);
        $this->assertEquals(Arr::get($input, 'version.footer_fg_photo_id'), $draft_version->footer_fg_photo_id);
        $this->assertEquals(Arr::get($input, 'version.footer_bg_photo_id'), $draft_version->footer_bg_photo_id);
        $this->assertEquals(Arr::get($input, 'version.publish_at'), $draft_version->publish_at);
        $this->assertEquals(Arr::get($input, 'version.signed_url'), $draft_version->signed_url);
        $this->assertEquals(Arr::get($input, 'version.sibling_nav'), $draft_version->sibling_nav);
        $this->assertEquals(Arr::get($input, 'version.randomize'), $draft_version->randomize);

        $page->publish();

        $page->refresh();

        $this->assertNotNull($page->published_at);
        $this->assertEquals($draft_version->id, $page->publishedVersion->id);
    }

    public function test_a_versionable_can_get_its_current_version()
    {
        $this->signInAdmin();
        $this->enableEditing();
        $page = $this->getModel();
        $this->assertEquals(1, $page->versions->count());
        $version = $page->versions->first();
        $this->assertNull($version->published_at);
        $this->assertEquals($version->id, $page->getDraftVersion()->id);

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

        $this->assertEquals($version->id, $page->version->id);
        $page->publish();
        $page->refresh();
        $version->refresh();

        $this->assertEquals($version->id, $page->version->id);

        $draft_version = $page->getDraftVersion();
        $page->refresh();
        $version->refresh();

        // we get the published version when not signed in
        auth()->logout();
        $this->assertNotEquals($version->id, $draft_version->id);
        $this->assertEquals($version->id, $page->publishedVersion->id);

        // when signed in as an admin we should see draft versions as the active version
        $this->signInAdmin();
        $this->enableEditing();
        $this->assertEquals($draft_version->id, $page->version->id);
    }

    public function test_creating_a_new_draft_version_copies_the_published_version_attributes()
    {
        $this->signInAdmin();
        $this->enableEditing();
        $page = Page::factory()->create();

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

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

        $bg_file_name = Str::random().'.jpg';
        $bg_file = UploadedFile::fake()->image($bg_file_name);
        $bg_file_upload = (new FileUpload())->saveFile($bg_file, 'photos', true);

        $fg_photo = Photo::factory()->create([
            'file_upload_id' => $fg_file_upload->id,
            'content_id' => $version->id,
            'content_type' => get_class($version),
        ]);

        $bg_photo = Photo::factory()->create([
            'file_upload_id' => $bg_file_upload->id,
            'content_id' => $version->id,
            'content_type' => get_class($version),
        ]);

        $version->slug = 'foobar';
        $version->theme = 'dark';
        $version->redirect = $this->faker->url;
        $version->footer_color = $this->faker->rgbcolor;
        $version->footer_fg_photo_id = $fg_photo->id;
        $version->footer_bg_photo_id = $bg_photo->id;
        $version->randomize = 5;

        $version->save();

        $this->assertNotNull($version->name);
        $this->assertNotNull($version->title);
        $this->assertNotNull($version->slug);
        $this->assertNotNull($version->parent_page_id);
        $this->assertNotNull($version->theme);
        $this->assertNotNull($version->sort_order);
        $this->assertNotNull($version->unlisted);
        $this->assertNotNull($version->redirect);
        $this->assertNotNull($version->show_sub_menu);
        $this->assertNotNull($version->footer_color);
        $this->assertNotNull($version->footer_fg_photo_id);
        $this->assertNotNull($version->footer_bg_photo_id);
        $this->assertNotNull($version->description);
        $this->assertNotNull($version->search_exclude);
        $this->assertNotNull($version->signed_url);
        $this->assertNotNull($version->sibling_nav);
        $this->assertNotNull($version->randomize);

        $page->publish();
        $this->assertInstanceOf(Version::class, $page->version);
        $this->assertNotNull($page->version->published_at);

        $published_version = $page->version;

        $draft_version = $page->getDraftVersion();
        $this->assertInstanceOf(Version::class, $draft_version);
        $this->assertNull($draft_version->published_at);

        $this->assertEquals($draft_version->id, $page->version->id);

        $this->assertEquals($published_version->name, $draft_version->name);
        $this->assertEquals($published_version->title, $draft_version->title);
        $this->assertEquals($published_version->slug, $draft_version->slug);
        $this->assertEquals($published_version->parent_page_id, $draft_version->parent_page_id);
        $this->assertEquals($published_version->theme, $draft_version->theme);
        $this->assertEquals($published_version->sort_order, $draft_version->sort_order);
        $this->assertEquals($published_version->unlisted, $draft_version->unlisted);
        $this->assertEquals($published_version->redirect, $draft_version->redirect);
        $this->assertEquals($published_version->show_sub_menu, $draft_version->show_sub_menu);
        $this->assertEquals($published_version->footer_color, $draft_version->footer_color);
        $this->assertEquals($published_version->footerFgPhoto->fileUpload->name, $draft_version->footerFgPhoto->fileUpload->name);
        $this->assertEquals($published_version->footerBgPhoto->fileUpload->name, $draft_version->footerBgPhoto->fileUpload->name);
        $this->assertEquals($published_version->description, $draft_version->description);
        $this->assertEquals($published_version->search_exclude, $draft_version->search_exclude);
        $this->assertEquals($published_version->signed_url, $draft_version->signed_url);
        $this->assertEquals($published_version->sibling_nav, $draft_version->sibling_nav);
        $this->assertEquals($published_version->randomize, $draft_version->randomize);
    }

    public function test_a_page_can_get_text_from_a_slug()
    {
        $slug = 'why-brentwood/support';
        $result = $this->getModel()->getTextFromSlug($slug);
        $this->assertEquals('Support', $result);

        $slug = 'students/course-selection/grade-10';
        $result = $this->getModel()->getTextFromSlug($slug);
        $this->assertEquals('Grade 10', $result);
    }
}
