<?php

namespace Tests\Feature;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Event;
use Carbon\Carbon;

use App\Events\PageSaved;
use App\Events\BlogSaved;

use App\Models\Page;
use App\Models\Blog;
use App\Models\Version;
use App\Models\ContentElement;
use App\Models\User;
use App\Models\TextBlock;

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

    public function test_a_page_can_be_published()
    {
        $page = $this->getModel();
        $content_element = $this->createContentElement(TextBlock::factory(), $page);

        $this->assertNull($page->published_at);

        $this->json('POST', route(Str::plural($this->getClassname()).'.publish', ['id' => $page->id]))
            ->assertStatus(401);

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

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.publish', ['id' => $page->id]))
            ->assertStatus(403);

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

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

        $this->assertNotNull($name);

        $this->json('POST', route(Str::plural($this->getClassname()).'.publish', ['id' => $page->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Str::title($this->getClassname()).' Published',
                'name' => $name,
                'id' => $page->id,
                'id' => $content_element->id
             ]);

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

        $this->assertNotNull($page->published_version_id);
        $this->assertEquals($page->published_version_id, $content_element->contentables()->first()->version_id);
    }

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

        $content_element = $this->createContentElement(TextBlock::factory(), $this->getModel());
        $text_block = $content_element->content;
        $old_text = $text_block->body;

        $page = $content_element->{Str::plural($this->getClassname())}()->first();

        $this->assertInstanceOf(get_class($this->getModel()), $page);

        $draft_version = $page->getDraftVersion();

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

        $this->assertEquals($page->draft_version_id, $content_element->getPageVersion($page)->id);

        $page->publish();
        $page->refresh();
        $content_element->refresh();
        $content = $content_element->content;

        $this->assertEquals($page->published_version_id, $content_element->getPageVersion($page)->id);

        $new_text_block = TextBlock::factory()->raw();
        $new_text = Arr::get($new_text_block, 'body');
        $this->assertNotNull($new_text);
        $input = $this->createContentElement(TextBlock::factory())->toArray();
        $input['type'] = 'text-block';
        $input['content'] = $new_text_block;
        $input['content']['id'] = $content->id;

        $input['pivot'] = $this->getContentableArray($page, [
            'sort_order' => $this->faker->randomNumber(1),
        ]);
        /*
            'contentable_id' => $page->id,
            'contentable_type' => get_class($page),
            'sort_order' => $this->faker->randomNumber(1),
            'unlisted' => false,
            'expandable' => null,
            'guest' => false,
        ];
         */

        $saved_content_element = (new ContentElement())->saveContentElement($input, $content_element->id);

        $this->assertNotEquals($page->getDraftVersion()->id, $page->publishedVersion->id);
        $this->assertNotEquals($content_element->id, $saved_content_element->id);
        $this->assertNotEquals($content->id, $saved_content_element->content->id);

        $this->withoutExceptionHandling();
        $this->assertEquals($page->getDraftVersion()->id, $saved_content_element->getPageVersion($page)->id);
        $page->publish();

        $page->refresh();

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

        if ($page instanceof Blog) {
            $route = Str::plural($this->getClassname()).'/'.$page->getSlug().'?version_id='.$draft_version->id;
        } else {
            $route = route(Str::plural($this->getClassname()).'.load', ['page' => $page->getSlug(), 'version_id' => $draft_version->id]);
        }

        $this->assertTrue(Str::contains($route, 'version_id'));

        $this->disableEditing();

        $this->get($route)
            ->assertSuccessful()
            ->assertDontSee($new_text)
            ->assertSee($old_text);
        //$this->assertTrue(editing());
    }

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

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

        $this->assertEquals(1, $page->versions()->count());
        $old_version = $page->versions()->first();
        $old_version->published_at = now()->subMinutes(5);
        $old_version->save();

        $page->refresh();

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

        $published_at = $page->published_at;

        $this->assertInstanceOf(Carbon::class, $published_at);

        $input = Page::factory()->raw();

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

        $content = $content_element->content;
        $new_text_block = TextBlock::factory()->raw();
        $new_text = Arr::get($new_text_block, 'body');
        $this->assertNotNull($new_text);
        $input = $this->createContentElement(TextBlock::factory())->toArray();
        $input['type'] = 'text-block';
        $input['content'] = $new_text_block;
        $input['content']['id'] = $content->id;

        $input['pivot'] = $this->getContentableArray($page, [
            'sort_order' => $this->faker->randomNumber(1),
        ]);
        /*
            'contentable_id' => $page->id,
            'contentable_type' => get_class($page),
            'sort_order' => $this->faker->randomNumber(1),
            'unlisted' => false,
            'expandable' => null,
            'guest' => false,
        ];
         */

        $saved_content_element = (new ContentElement())->saveContentElement($input, $content_element->id);

        $page->refresh();

        $this->assertTrue($page->can_be_published);

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.publish', ['id' => $page->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Str::title($this->getClassname()).' Published',
             ]);

        $page->refresh();

        $this->assertEquals(2, $page->versions()->count());

        $new_version = $page->versions()->get()->last();

        $this->assertNotEquals($old_version->id, $new_version->id);
        $old_pub = $old_version->published_at;
        $new_pub = $new_version->published_at;

        $this->assertNotEquals($old_pub, $page->published_at);
        $this->assertEquals($new_pub, $page->published_at);
    }

    public function test_when_a_page_is_published_an_event_is_broadcast()
    {
        Event::fake();
        $page = $this->getModel();

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

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.publish', ['id' => $page->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Str::title($this->getClassname()).' Published',
             ]);

        $page->refresh();
        $this->assertNotNull($page->published_version_id);

        if ($this->getClassname() === 'page') {
            Event::assertDispatched(function (PageSaved $event) use ($page) {
                return $event->{$this->getClassname()}->id === $page->id;
            });
        } elseif ($this->getClassname() === 'blog') {
            Event::assertDispatched(function (BlogSaved $event) use ($page) {
                return $event->{$this->getClassname()}->id === $page->id;
            });
        }
    }

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

        $content_element = $this->createContentElement(TextBlock::factory(), $this->getModel());
        $page = $content_element->{Str::plural($this->getClassname())}()->first();

        $input['version'] = Version::factory()->{$this->getClassname()}()->randomize()->raw();

        $this->json('POST', route(Str::plural($this->getClassname()).'.update', ['id' => $page->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => Arr::get($input, 'version.name').' Saved',
             ]);

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

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

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

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

        $redirect_page = Page::factory()->create();
        $redirect_page->publish();

        $input['version'] = Version::factory()->{$this->getClassname()}()->raw();
        $input['version']['redirect'] = $redirect_page->id;

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

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.update', ['id' => $page->id]), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => Arr::get($input, 'version.name').' Saved',
            ]);

        $page->refresh();

        $this->assertNotNull($page->version->redirect);
        $this->assertEquals($redirect_page->id, $page->version->redirect);
        $page->publish();

        $this->disableEditing();

        $this->get($page->full_slug)
            ->assertRedirect($redirect_page->full_slug);
    }
}
