<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

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

use Tests\Feature\SoftDeletesTestTrait;
use Tests\Feature\VersioningTestTrait;
use Tests\Feature\PagesTestTrait;

class BlogTest extends TestCase
{
    use WithFaker;
    use SoftDeletesTestTrait;
    use VersioningTestTrait;
    use PagesTestTrait;
    use PublicationsTestTrait;

    protected function getModel()
    {
        return Blog::factory()->create();
    }

    protected function getClassname()
    {
        return 'blog';
    }

    /*
    public function the_blog_managing_page_can_be_loaded()
    {
        $this->get(route('blogs.manage'))
             ->assertRedirect('/login');

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

        $this->withoutExceptionHandling();
        $this->get(route('blogs.manage'))
             ->assertRedirect('/');

        $this->signInAdmin();

        $this->get(route('blogs.manage'))
             ->assertSuccessful();
    }

    /*
    public function a_blog_article_can_be_created()
    {
        $input = Blog::factory()->raw();
        $input['version'] = Version::factory()->blog()->raw();

        $this->postJson(route('blogs.store'), [])
            ->assertStatus(401);

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

        $this->postJson(route('blogs.store'), $input)
            ->assertStatus(403);

        $user->addRole('blogs-editor');
        $user->refresh();

        $this->json('POST', route('blogs.store'), [])
            ->assertStatus(422)
            ->assertJsonValidationErrors([
                'version.name',
            ]);

        $this->withoutExceptionHandling();
        $this->postJson(route('blogs.store'), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => Arr::get($input, 'version.name').' Saved',
            ]);

        $blog = Blog::all()->last();

        $this->assertEquals(1, $blog->versions()->count());
        cache()->tags([cache_name($blog)])->flush();

        $this->assertNotNull($blog->version->name);
        $this->assertNotNull($blog->version->title);
        $this->assertNotNull($blog->author);

        $this->assertEquals(Arr::get($input, 'version.name'), $blog->version->name);
        $this->assertEquals(Arr::get($input, 'version.title'), $blog->version->title);
        $this->assertEquals(Arr::get($input, 'author'), $blog->author);
    }

    /*
    public function a_blog_can_be_updated()
    {
        $blog = Blog::factory()->create();
        $input = Blog::factory()->raw();
        $input['version'] = Version::factory()->blog()->raw();

        $this->postJson(route('blogs.update', ['id' => $blog->id]), [])
            ->assertStatus(401);

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

        $this->postJson(route('blogs.update', ['id' => $blog->id]), [])
            ->assertStatus(422)
            ->assertJsonValidationErrors([
                'version.name',
            ]);

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

        $blog->refresh();

        $this->assertEquals(Arr::get($input, 'version.name'), $blog->name);
        $this->assertEquals(Arr::get($input, 'author'), $blog->author);
    }

    /*
    public function blogs_can_be_loaded_for_pagination()
    {
        $blog = Blog::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $blog);

        $this->assertInstanceOf(Blog::class, $blog);
        $this->assertTrue($blog->contentElements()->get()->contains('id', $content_element->id));

        $this->json('GET', route('blogs.manage'))
            ->assertStatus(401);

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

        $this->json('GET', route('blogs.manage'))
            ->assertStatus(403);

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

        $this->json('GET', route('blogs.manage'))
             ->assertSuccessful();
    }

    /*
    public function unpublished_blogs_can_be_listed_for_blog_managers()
    {
        $blog = Blog::factory()->create();

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

        $this->get(route('blogs.manage'))
             ->assertRedirect('/login');

        $this->signIn($user);

        $this->get(route('blogs.manage'))
             ->assertRedirect('/');

        $user->addRole('blogs-manager');
        $user->refresh();

        $this->get(route('blogs.manage'))
            ->assertSuccessful();

        $this->assertNotNull($blog->name);
        $this->assertNotNull($blog->getSlug());
    }

    /*
    public function a_list_of_blogs_can_be_loaded()
    {
        $blog = Blog::factory()->create();
        $blog->publish();
        $blog->refresh();
        $blog_unlisted = Blog::factory()->create();
        $version = $blog_unlisted->versions->first();
        $version->unlisted = true;
        $version->save();
        $blog_unlisted->publish();

        $blog_unpublished = Blog::factory()->create();

        //cache()->tags([cache_name($blog)])->flush();
        //cache()->tags([cache_name($blog_unlisted)])->flush();

        $this->assertNotNull($blog->published_version_id);

        $this->withoutExceptionHandling();
        $this->json('POST', route('blogs.paginate'), ['paginate_count' => Blog::all()->count()])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog->version->name,
            ])
            ->assertJsonMissing([
                'name' => $blog_unlisted->version->name,
                'name' => $blog_unpublished->versions()->first()->name,
            ]);
    }

    /*
    public function blog_listings_can_be_fitlered_by_tags()
    {
        $blog1 = Blog::factory()->create();
        $tag1 = Tag::factory()->create();
        $blog1->addTag($tag1);
        $blog1->publish();
        $blog1->refresh();

        $blog2 = Blog::factory()->create();
        $tag2 = Tag::factory()->create();
        $blog2->addTag($tag2);
        $blog2->publish();
        $blog2->refresh();

        $blog3 = Blog::factory()->create();
        $tag3 = Tag::factory()->create();
        $blog3->addTag($tag3);
        $blog3->publish();
        $blog3->refresh();

        $this->withoutExceptionHandling();
        $this->json('POST', route('blogs.paginate'), ['paginate_count' => Blog::all()->count()])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog1->version->name,
                'name' => $blog2->version->name,
                'name' => $blog3->version->name,
            ]);

        $this->json('POST', route('blogs.paginate'), ['tags' => [$tag1], 'paginate_count' => Blog::all()->count()])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog1->version->name,
            ])
            ->assertJsonMissing([
                'name' => $blog2->version->name,
                'name' => $blog3->version->name,
            ]);

        $this->json('POST', route('blogs.paginate'), ['tags' => [$tag1, $tag2], 'paginate_count' => Blog::all()->count()])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog1->version->name,
                'name' => $blog2->version->name,
            ])
            ->assertJsonMissing([
                'name' => $blog3->version->name,
            ]);
    }

    /*
    public function signed_url_blogs_shouldnt_show_up_in_blog_lists()
    {
        $this->signInAdmin();
        $this->enableEditing();

        $tag = Tag::factory()->create();
        $blog1 = Blog::factory()->create();
        $blog2 = Blog::factory()->create();
        $version = $blog2->versions()->first();

        $version->signed_url = true;
        $version->save();

        $blog1->addTag($tag);
        $blog2->addTag($tag);

        $blog1->publish();
        $blog2->publish();

        auth()->logout();
        session()->invalidate();

        $this->assertEquals(1, $blog2->version->signed_url);

        $this->json('POST', route('blogs.paginate'), ['tags' => [ $tag ], 'sort_by' => 'published_at', 'descending' => 'true'])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog1->version->name,
            ])
            ->assertJsonMissing([
                'name' => $blog2->version->name,
            ]);
    }
     */

    public function test_blog_can_be_viewed()
    {
        $blog = Blog::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $blog);

        $blog->publish();

        $this->withoutExceptionHandling();
        $this->get($blog->full_slug)
            ->assertSuccessful();
    }

    public function test_creating_a_blog_should_turn_on_editing_in_the_session()
    {
        $this->signInAdmin();
        $this->assertFalse(editing());

        $input = Blog::factory()->raw();
        $input['version'] = Version::factory()->blog()->raw();

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

        $this->assertTrue(editing());
    }

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

        $this->withoutExceptionHandling();
        $this->json('POST', route('blogs.paginate'), ['descending' => true, 'limit' => 10])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $blog->version->name,
             ]);

        $tag = Tag::factory()->create();
        $tag2 = Tag::factory()->create();
        $blog->addTag($tag);

        $this->json('POST', route('blogs.paginate'), ['tags' => [$tag], 'limit' => 10, 'descending' => true])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $blog->version->name,
             ]);

        $this->json('POST', route('blogs.paginate'), ['tags' => [$tag2], 'limit' => 10, 'descending' => true])
             ->assertSuccessful()
             ->assertJsonMissing([
                'name' => $blog->version->name,
             ]);
    }

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

        $this->json('POST', route('blogs.paginate'), ['limit' => 10, 'descending' => true])
             ->assertSuccessful()
            ->assertJsonFragment(['content' => $blog->content->toArray()]);
    }

    public function test_a_blog_can_be_searched_for()
    {
        $blog = Blog::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $blog);
        $this->assertNotNull($content_element->content->header);

        $blog->publish();

        $this->json('POST', route('blogs.search'), ['terms' => $blog->version->name])
             ->assertSuccessful()
            ->assertJsonFragment([
                'header' => $content_element->content->header,
            ]);
    }

    public function test_blog_search_results_are_filtered_by_content_element_tags()
    {
        $this->signInAdmin();
        $this->enableEditing();
        $terms = $this->faker->firstName;
        $blog1 = Blog::factory()->create();
        $content_element1 = $this->createContentElement(TextBlock::factory(), $blog1);
        $header1 = $content_element1->content->header;
        $version1 = $blog1->version;
        $version1->name .= $terms;
        $version1->save();

        $blog2 = Blog::factory()->create();
        $content_element2 = $this->createContentElement(TextBlock::factory(), $blog2);
        $header2 = $content_element2->content->header;
        $version2 = $blog2->version;
        $version2->name .= $terms;
        $version2->save();

        $tag = Tag::factory()->create();
        $blog2->addTag($tag);

        $blog1->publish();
        $blog2->publish();

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

        $this->json('POST', route('blogs.search'), ['terms' => $terms])
             ->assertSuccessful()
            ->assertJsonFragment([
                'header' => $header1,
                'header' => $header2,
            ]);

        $this->json('POST', route('blogs.search'), ['terms' => $terms, 'tags' => [$tag->toArray()]])
             ->assertSuccessful()
             ->assertJsonMissing([
                'header' => $header1,
             ])
            ->assertJsonFragment([
                'header' => $header2,
            ]);
    }

    public function test_no_search_results_doesnt_load_all_blogs()
    {
        $blog = Blog::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $blog);
        $this->assertNotNull($content_element->content->header);

        $blog->publish();

        $this->json('POST', route('blogs.search'), ['terms' => Str::random()])
             ->assertSuccessful()
            ->assertJsonFragment([
                'total' => 0,
            ])
            ->assertJsonMissing([
                'header' => $content_element->content->header,
            ]);
    }

    public function test_blogs_can_be_excluded_by_id_when_paginating()
    {
        $blog = Blog::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $blog);
        $this->assertNotNull($content_element->content->header);

        $blog2 = Blog::factory()->create();
        $content_element2 = $this->createContentElement(TextBlock::factory(), $blog2);
        $this->assertNotNull($content_element2->content->header);

        $blog->publish();
        $blog2->publish();

        $this->json('POST', route('blogs.paginate'), ['limit' => 100, 'descending' => true, 'exclude_ids' => [$blog2->id]])
             ->assertSuccessful()
             ->assertJsonFragment([
                'header' => $content_element->content->header,
             ])
             ->assertJsonMissing([
                'header' => $content_element2->content->header,
             ]);
    }

    public function test_blogs_can_be_exclude_by_tag()
    {
        $blog1 = Blog::factory()->create();
        $tag1 = Tag::factory()->create();
        $blog1->addTag($tag1);
        $blog1->publish();
        $blog1->refresh();

        $blog2 = Blog::factory()->create();
        $tag2 = Tag::factory()->create();
        $blog2->addTag($tag2);
        $blog2->publish();
        $blog2->refresh();

        $blog3 = Blog::factory()->create();
        $tag3 = Tag::factory()->create();
        $blog3->addTag($tag3);
        $blog3->publish();
        $blog3->refresh();

        $this->withoutExceptionHandling();
        $this->json('POST', route('blogs.paginate'), [
            'paginate_count' => 100,
            'descending' => true,
            'tags' => [
                   ['id' => $tag2->id, 'pivot' => ['exclude' =>  true ]],
                ]
            ])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog1->version->name,
                'name' => $blog3->version->name,
            ])
            ->assertJsonMissing([
                'name' => $blog2->version->name,
            ]);
    }

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

        $this->assertNotNull($blog->name);

        $this->json('POST', route('blogs.paginate'), [
                'paginate_count' => 5,
                'sort_by' => 'created_at',
                'descending' => 'true',
            ])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog->name,
                'id' => $blog->id,
            ]);

        $this->json('POST', route('blogs.paginate'), [
                'paginate_count' => 5,
                'sort_by' => 'id',
                'descending' => 'true',
            ])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $blog->name,
                'id' => $blog->id,
            ]);
    }


    public function test_a_blog_can_update_its_first_published_at()
    {
        $blog = $this->getModel();
        $blog->publish();

        $this->json('POST', route('blogs.update-published-at', ['id' => $blog->id]), [])
            ->assertStatus(401);

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

        $this->json('POST', route('blogs.update-published-at', ['id' => $blog->id]), [])
            ->assertStatus(403);

        $user->createPermission('update', $blog);

        $this->json('POST', route('blogs.update-published-at', ['id' => $blog->id]), [])
             ->assertStatus(422)
            ->assertJsonValidationErrors([
                'published_at',
            ]);

        $published_at = now()->subMinutes(5)->roundUnit('seconds');
        $input = [
            'published_at' => $published_at,
        ];

        $this->json('POST', route('blogs.update-published-at', ['id' => $blog->id]), $input)
             ->assertSuccessful()
            ->assertJsonFragment([
                'success' => 'Published At Updated',
            ]);

        $blog->refresh();

        $this->assertEquals($published_at, $blog->first_published_at);
    }
}
