<?php

namespace Tests\Feature;

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

use App\Models\User;
use App\Models\Role;
use App\Models\Version;
use App\Models\TextBlock;
use App\Models\Tag;

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

    public function test_the_publication_managing_page_can_be_loaded()
    {
        $this->get(route(Str::plural($this->getClassname()).'.manage'))
             ->assertRedirect('/login');

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

        $this->withoutExceptionHandling();
        $this->get(route(Str::plural($this->getClassname()).'.manage'))
             ->assertRedirect('/');

        $this->signInAdmin();

        $this->get(route(Str::plural($this->getClassname()).'.manage'))
             ->assertSuccessful();
    }

    public function test_a_publication_article_can_be_created()
    {
        $classname = get_class($this->getModel());

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

        $this->postJson(route(Str::plural($this->getClassname()).'.store'), [])
            ->assertStatus(401);

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

        $this->postJson(route(Str::plural($this->getClassname()).'.store'), $input)
            ->assertStatus(403);

        $user->addRole(Str::plural($this->getClassname()).'-editor');
        $user->refresh();
        $this->assertTrue($user->hasRole(Str::plural($this->getClassname()).'-editor'));
        $this->assertTrue($user->can('create', new $classname()));

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

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

        $model = (new $classname())->all()->last();

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

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

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

    public function test_a_publication_can_be_updated()
    {
        $publication = $this->getModel();
        $classname = get_class($this->getModel());
        $input = $classname::factory()->raw();
        $input['version'] = Version::factory()->blog()->raw();

        $this->postJson(route(Str::plural($this->getClassname()).'.update', ['id' => $publication->id]), [])
            ->assertStatus(401);

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

        $this->postJson(route(Str::plural($this->getClassname()).'.update', ['id' => $publication->id]), [])
            ->assertStatus(422)
            ->assertJsonValidationErrors([
                'version.name',
            ]);

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

        $publication->refresh();

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

    public function test_publications_can_be_loaded_for_pagination()
    {
        $publication = $this->getModel();
        $classname = get_class($this->getModel());
        $content_element = $this->createContentElement(TextBlock::factory(), $publication);

        $this->assertInstanceOf($classname, $publication);
        $this->assertTrue($publication->contentElements()->get()->contains('id', $content_element->id));

        $this->json('GET', route(Str::plural($this->getClassname()).'.manage'))
            ->assertStatus(401);

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

        $this->json('GET', route(Str::plural($this->getClassname()).'.manage'))
            ->assertStatus(403);

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

        $this->json('GET', route(Str::plural($this->getClassname()).'.manage'))
             ->assertSuccessful();
    }

    public function test_unpublished_publications_can_be_listed_for_managers()
    {
        $publication = $this->getModel();
        $classname = get_class($this->getModel());
        $user = User::factory()->create();

        $this->get(route(Str::plural($this->getClassname()).'.manage'))
             ->assertRedirect('/login');

        $this->signIn($user);

        $this->get(route(Str::plural($this->getClassname()).'.manage'))
             ->assertRedirect('/');

        $user->addRole(Str::plural($this->getClassname()).'-manager');
        $user->refresh();

        $this->get(route(Str::plural($this->getClassname()).'.manage'))
            ->assertSuccessful();

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

    public function test_a_list_of_publications_can_be_loaded()
    {
        $publication = $this->getModel();
        $classname = get_class($this->getModel());
        $publication->publish();
        $publication->refresh();
        $publication_unlisted = $this->getModel();
        $version = $publication_unlisted->versions->first();
        $version->unlisted = true;
        $version->save();
        $publication_unlisted->publish();

        $publication_unpublished = $this->getModel();

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

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['limit' => 100, 'descending' => true])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication->version->name,
            ])
            ->assertJsonMissing([
                'name' => $publication_unlisted->version->name,
                'name' => $publication_unpublished->versions()->first()->name,
            ]);
    }

    public function test_publication_listings_can_be_fitlered_by_tags()
    {
        $publication1 = $this->getModel();
        $classname = get_class($publication1);
        $tag1 = Tag::factory()->create();
        $publication1->addTag($tag1);
        $publication1->publish();
        $publication1->refresh();

        $publication2 = $this->getModel();
        $tag2 = Tag::factory()->create();
        $publication2->addTag($tag2);
        $publication2->publish();
        $publication2->refresh();

        $publication3 = $this->getModel();
        $tag3 = Tag::factory()->create();
        $publication3->addTag($tag3);
        $publication3->publish();
        $publication3->refresh();

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['limit' => 100, 'descending' => true])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication1->version->name,
                'name' => $publication2->version->name,
                'name' => $publication3->version->name,
            ]);

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag1], 'limit' => 100, 'descending' => true])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication1->version->name,
            ])
            ->assertJsonMissing([
                'name' => $publication2->version->name,
                'name' => $publication3->version->name,
            ]);

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag1, $tag2], 'limit' => 100, 'descending' => true])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication1->version->name,
                'name' => $publication2->version->name,
            ])
            ->assertJsonMissing([
                'name' => $publication3->version->name,
            ]);
    }

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

        $tag = Tag::factory()->create();
        $publication1 = $this->getModel();
        $publication2 = $this->getModel();
        $version = $publication2->versions()->first();

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

        $publication1->addTag($tag);
        $publication2->addTag($tag);

        $publication1->publish();
        $publication2->publish();

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

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [ $tag ], 'sort_by' => 'published_at', 'descending' => 'true'])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication1->version->name,
            ])
            ->assertJsonMissing([
                'name' => $publication2->version->name,
            ]);
    }

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

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

        $this->withoutExceptionHandling();
        $this->get($publication->full_slug)
             ->assertSuccessful()
            ->assertSee($content_element->content->header);
    }

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

        $classname = get_class($this->getModel());
        $input = $classname::factory()->raw();
        $input['version'] = Version::factory()->blog()->raw();

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

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

    public function test_publications_can_be_pagianted()
    {
        $classname = get_class($this->getModel());
        $publication = $this->getModel();
        $publication->publish();

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['limit' => 100, 'descending' => true])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $publication->version->name,
             ]);

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag], 'limit' => 100, 'descending' => true])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $publication->version->name,
             ]);

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag2], 'limit' => 100, 'descending' => true])
             ->assertSuccessful()
             ->assertJsonMissing([
                'name' => $publication->version->name,
             ]);
    }

    public function test_paginating_publications_contains_its_content_attribute()
    {
        $publication = $this->getModel();
        $publication->publish();
        $classname = get_class($publication);

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['limit' => 100, 'descending' => true])
             ->assertSuccessful()
            ->assertJsonFragment(['content' => $publication->content->toArray()]);
    }

    public function test_a_publication_can_be_searched_for()
    {
        $publication = $this->getModel();
        $content_element = $this->createContentElement(TextBlock::factory(), $publication);
        $this->assertNotNull($content_element->content->header);

        $publication->publish();

        $this->json('POST', route(Str::plural($this->getClassname()).'.search'), ['terms' => $publication->version->name])
             ->assertSuccessful()
            ->assertJsonFragment([
                'header' => $content_element->content->header,
            ]);
    }

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

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

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

        $publication1->publish();
        $publication2->publish();

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

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

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

    public function test_no_search_results_doesnt_load_all_publications()
    {
        $publication = $this->getModel();
        $content_element = $this->createContentElement(TextBlock::factory(), $publication);
        $this->assertNotNull($content_element->content->header);

        $publication->publish();

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

    public function test_publicationss_can_be_excluded_by_id_when_paginating()
    {
        $publication = $this->getModel();
        $classname = get_class($publication);
        $content_element = $this->createContentElement(TextBlock::factory(), $publication);
        $this->assertNotNull($content_element->content->header);

        $publication2 = $this->getModel();
        $content_element2 = $this->createContentElement(TextBlock::factory(), $publication2);
        $this->assertNotNull($content_element2->content->header);

        $publication->publish();
        $publication2->publish();

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

    public function test_publicationss_can_be_exclude_by_tag()
    {
        $publication1 = $this->getModel();
        $classname = get_class($publication1);
        $tag1 = Tag::factory()->create();
        $publication1->addTag($tag1);
        $publication1->publish();
        $publication1->refresh();

        $publication2 = $this->getModel();
        $tag2 = Tag::factory()->create();
        $publication2->addTag($tag2);
        $publication2->publish();
        $publication2->refresh();

        $publication3 = $this->getModel();
        $tag3 = Tag::factory()->create();
        $publication3->addTag($tag3);
        $publication3->publish();
        $publication3->refresh();

        $this->withoutExceptionHandling();
        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), [
            'limit' => 100,
            'descending' => true,
            'tags' => [
                   ['id' => $tag2->id, 'pivot' => ['exclude' =>  true ]],
                ]
            ])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication1->version->name,
                'name' => $publication3->version->name,
            ])
            ->assertJsonMissing([
                'name' => $publication2->version->name,
            ]);
    }

    public function test_publications_can_be_sorted()
    {
        $publication = $this->getModel();
        $publication->publish();

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), [
                'limit' => 5,
                'sort_by' => 'created_at',
                'descending' => 'true',
            ])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication->name,
                'id' => $publication->id,
            ]);

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), [
                'limit' => 5,
                'sort_by' => 'id',
                'descending' => 'true',
            ])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication->name,
                'id' => $publication->id,
            ]);
    }


    public function test_a_publication_can_update_its_first_published_at()
    {
        $publication = $this->getModel();
        $publication->publish();

        $this->json('POST', route(Str::plural($this->getClassname()).'.update-published-at', ['id' => $publication->id]), [])
            ->assertStatus(401);

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.update-published-at', ['id' => $publication->id]), [])
            ->assertStatus(403);

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.update-published-at', ['id' => $publication->id]), [])
             ->assertStatus(422)
            ->assertJsonValidationErrors([
                'published_at',
            ]);

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.update-published-at', ['id' => $publication->id]), $input)
             ->assertSuccessful()
            ->assertJsonFragment([
                'success' => 'Published At Updated',
            ]);

        $publication->refresh();

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

    /* TODO
    public function loading_publications_filters_them_by_permissions(): void
    {
        $publication = $this->getModel();
        $tag = Tag::factory()->create();
        $publication->addTag($tag);
        $publication->publish();
        $classname = get_class($publication);

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

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag], 'paginate_count' => $classname::all()->count()])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication->version->name,
            ]);

        $this->assertFalse($user->can('view', $publication));

        $publication->createPermission('view', $role);
        $this->assertTrue($publication->hasViewPermissions());

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag], 'paginate_count' => $classname::all()->count()])
            ->assertSuccessful()
            ->assertJsonMissing([
                'name' => $publication->version->name,
            ]);

        $this->signIn($user);
        $user->refresh();

        $this->assertTrue($user->can('view', $publication));

        $this->json('POST', route(Str::plural($this->getClassname()).'.paginate'), ['tags' => [$tag], 'paginate_count' => $classname::all()->count()])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $publication->version->name,
            ]);
    }
     */
}
