<?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\Course;
use App\Models\Tag;
use App\Models\User;
use App\Models\Version;
use App\Models\ContentElement;
use App\Models\CourseDescription;
use App\Models\TextBlock;
use App\Models\PhotoBlock;
use App\Models\Page;
use App\Models\CourseList;

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

class CourseTest extends TestCase
{
    use WithFaker;
    use SoftDeletesTestTrait;
    use VersioningTestTrait;
    use PagesTestTrait;

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

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

    public function test_a_course_can_be_created()
    {
        $input = Course::factory()->raw();
        $tag = Tag::factory()->create();
        $input['tags'] = [ $tag ];

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

        $this->json('POST', route('courses.store'), $input)
             ->assertStatus(401);

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

        $this->json('POST', route('courses.store'), $input)
             ->assertStatus(403);

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

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

        $this->json('POST', route('courses.store'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                Arr::get($input, 'version.name').' Saved',
             ]);

        $course = Course::all()->last();

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

        $this->assertNotNull($course->version->name);

        $this->assertNotNull($course->tags);

        $this->assertEquals(Arr::get($input, 'version.name'), $course->version->name);
        $this->assertEquals($tag->id, $course->tags->first()->id);
    }

    public function test_a_course_can_be_edited()
    {
        $course = Course::factory()->create();

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

        $tag = Tag::factory()->create();
        $input['tags'] = [ $tag ];

        $input['content'][] = ContentElement::factory()->for(CourseDescription::factory(), 'content')->raw();
        $input['content'][0]['id'] = 0;
        $input['content'][0]['type'] = 'course-description';

        $header = $this->faker->name();
        $body = $this->faker->paragraph();
        $credit = $this->faker->sentence();

        $input['content'][0]['content'] = CourseDescription::factory()->raw([
            'header' => $header,
            'body' => $body,
            'credit' => $credit,
        ]);
        $input['content'][0]['pivot'] = $this->getContentableArray($course);

        $this->json('POST', route('courses.update', ['id' => $course->id]), [])
             ->assertStatus(401);

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

        $this->json('POST', route('courses.update', ['id' => $course->id]), [])
             ->assertStatus(403);

        $this->signInAdmin();

        $this->json('POST', route('courses.update', ['id' => $course->id]), [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'version.name',
             ]);

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

        $course->refresh();
        $this->assertEquals(Arr::get($input, 'version.name'), $course->version->name);

        $content_element = $course->contentElements()->first();

        $this->assertEquals('course-description', $content_element->type);
        $course_description = $content_element->content;

        $this->assertNotNull($course_description->header);
        $this->assertNotNull($course_description->body);
        $this->assertNotNull($course_description->credit);

        $this->assertEquals($header, $course_description->header);
        $this->assertEquals($body, $course_description->body);
        $this->assertEquals($credit, $course_description->credit);

        $this->assertEquals($tag->id, $course->tags->first()->id);
    }


    public function test_the_courses_index_can_be_loaded()
    {
        $course = Course::all()->random();

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

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

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

        $this->signInAdmin();

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

    public function test_a_course_can_be_searched_for()
    {
        $course = Course::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $course);
        $this->assertNotNull($content_element->content->header);
        $course->publish();

        $this->assertNotNull($course->version->name);

        $input = [
            'terms' => $course->version->name,
        ];

        $this->json('POST', route('courses.search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $course->version->name,
             ]);
    }

    public function test_a_course_can_be_searched_for_by_course_name()
    {
        $course = Course::factory()->create();
        $course->publish();

        $this->assertNotNull($course->version->name);

        $input = [
            'terms' => Str::substr($course->version->name, 0, 6),
            'filters' => [
                'blogs' => false,
                'courses' => true,
                'livestreams' => false,
                'pages' => false,
            ],
            'paginate' => 1,
            'descending' => true,
        ];

        $title = $course->getSearchResultTitle();
        $this->assertNotNull($title);

        $this->json('POST', route('search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $title,
             ]);
    }


    public function test_a_course_can_be_searched_for_by_tag()
    {
        $course12 = Course::factory()->create();
        $grade12_tag = Tag::where('name', 'Grade 12')->first();
        $course12->addTag($grade12_tag);
        $course12->publish();

        $course11 = Course::factory()->create();
        $grade11_tag = Tag::where('name', 'Grade 11')->first();
        $course11->addTag($grade11_tag);
        $course11->publish();

        $this->assertNotNull($course12->version->name);
        $this->assertNotNull($course11->version->name);

        $input = [
            'terms' => $grade12_tag->name,
            'paginate' => 1,
            'paginate_count' => 100,
        ];

        $this->json('POST', route('courses.search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $course12->version->name,
             ]);
        //->assertJsonMissing([
        //'name' => $course11->version->name,
        //]);
    }

    public function test_courses_can_be_loaded_for_pagination()
    {
        $course = Course::whereHas('version', function($query) {
            $query->whereNotNull('published_at');
        })
        ->get()
        ->sortBy(function($course) {
            return $course->version->name;
        })->first();

        $this->json('POST', route('courses.paginate'), ['sort_by' => 'name'])
             ->assertSuccessful()
             ->assertJsonFragment([
                'name' => $course->version->name,
             ]);
    }

    public function test_unpublished_courses_can_be_listed_for_course_managers()
    {
        $course = Course::factory()->create();

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

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

        $this->signIn($user);

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

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

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

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

    public function test_when_creating_a_new_course_also_create_a_new_course_description_for_it()
    {
        $input = Course::factory()->raw();
        $input['version'] = Version::factory()->course()->raw();

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

        $this->json('POST', route('courses.store'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                Arr::get($input, 'version.name').' Saved',
             ]);

        $course = Course::all()->last();

        $this->assertEquals(1, $course->contentElements()->count());
        $content_element = $course->contentElements()->first();
        $this->assertInstanceOf(CourseDescription::class, $content_element->content);
    }

    public function test_when_creating_a_new_course_we_can_pass_in_a_new_course_description_as_well()
    {
        $course_description_input = CourseDescription::factory()->raw();
        $input = Course::factory()->raw();
        $input['version'] = Version::factory()->course()->raw();
        $input['content'] = [
                [
                    'content' => $course_description_input,
                    'pivot' => [
                        'contentable_id' => null,
                        'contentable_type' => null,
                        'expandable' => null,
                        'filter' => 0,
                        'hide_print' => 0,
                        'guest' => 0,
                        'no_margin' => 0,
                        'randomize' => 0,
                        'sort_order' => 1,
                        'unlisted' => 0,
                    ],
                    'type' => 'course-description',
                    'id' => 0,
                ],
        ];
        $input['tags'] = [ Tag::all()->random()->toArray() ];

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

        $this->json('POST', route('courses.store'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                Arr::get($input, 'version.name').' Saved',
             ]);

        $course = Course::all()->last();

        $this->assertEquals(1, $course->contentElements()->count());
        $content_element = $course->contentElements()->first();
        $course_description = $content_element->content;
        $this->assertInstanceOf(CourseDescription::class, $course_description);
        $this->assertEquals(Arr::get($course_description_input, 'header'), $course_description->header);
        $this->assertEquals(Arr::get($course_description_input, 'body'), $course_description->body);
        $this->assertEquals(Arr::get($course_description_input, 'credit'), $course_description->credit);
    }

    public function test_course_lists_can_be_fitlered_by_tags()
    {
        $course1 = Course::factory()->create();
        $version = $course1->getDraftVersion();
        $version->name = 'zz-'.$version->name;
        $version->save();
        $tag1 = Tag::factory()->create();
        $course1->addTag($tag1);
        $course1->publish();
        $course1->refresh();

        $course2 = Course::factory()->create();
        $version = $course2->getDraftVersion();
        $version->name = 'zz-'.$version->name;
        $version->save();
        $tag2 = Tag::factory()->create();
        $course2->addTag($tag2);
        $course2->publish();
        $course2->refresh();

        $course3 = Course::factory()->create();
        $version = $course3->getDraftVersion();
        $version->name = 'zz-'.$version->name;
        $version->save();
        $tag3 = Tag::factory()->create();
        $course3->addTag($tag3);
        $course3->publish();
        $course3->refresh();

        $this->withoutExceptionHandling();
        $this->json('POST', route('courses.paginate'), ['sort_by' => 'name', 'descending' => true, 'paginate_count' => 25])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $course1->version->name,
                'name' => $course2->version->name,
                'name' => $course3->version->name,
            ]);

        $this->json('POST', route('courses.paginate'), ['tags' => [$tag1], 'sort_by' => 'name', 'descending' => true, 'paginate_count' => 25])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $course1->version->name,
            ])
            ->assertJsonMissing([
                'name' => $course2->version->name,
                'name' => $course3->version->name,
            ]);

        $this->json('POST', route('courses.paginate'), ['tags' => [$tag1, $tag2], 'sort_by' => 'name', 'descending' => true, 'paginate_count' => 25])
            ->assertSuccessful()
            ->assertJsonFragment([
                'name' => $course1->version->name,
                'name' => $course2->version->name,
            ])
            ->assertJsonMissing([
                'name' => $course3->version->name,
            ]);
    }

    public function test_courses_can_be_excluded_by_id_when_paginating()
    {
        $this->signInAdmin();
        $course = Course::factory()->create();
        $content_element = $this->createContentElement(TextBlock::factory(), $course);
        $this->assertNotNull($content_element->content->header);
        $version = $course->getDraftVersion();
        $version->name = 'aa-'.$version->name;
        $version->save();

        $course2 = Course::factory()->create();
        $content_element2 = $this->createContentElement(TextBlock::factory(), $course2);
        $this->assertNotNull($content_element2->content->header);
        $version2 = $course2->getDraftVersion();
        $version2->name = 'aa-'.$version2->name;
        $version2->save();

        $course->publish();
        $course2->publish();

        $this->json('POST', route('courses.paginate'), ['exclude_ids' => [$course2->id], 'sort_by' => 'name', 'paginate_count' => 25])
             ->assertSuccessful()
             ->assertJsonFragment([
                'header' => $content_element->content->header,
             ])
             ->assertJsonMissing([
                'header' => $content_element2->content->header,
             ]);
    }

    public function test_courses_can_be_exclude_by_tag()
    {
        $course1 = Course::factory()->create();
        $tag1 = Tag::factory()->create();
        $course1->addTag($tag1);
        $course1->publish();
        $course1->refresh();

        $course2 = Course::factory()->create();
        $tag2 = Tag::factory()->create();
        $course2->addTag($tag2);
        $course2->publish();
        $course2->refresh();

        $course3 = Course::factory()->create();
        $tag3 = Tag::factory()->create();
        $course3->addTag($tag3);
        $course3->publish();
        $course3->refresh();

        $this->withoutExceptionHandling();
        $this->json('POST', route('courses.paginate'), ['descending' => true,
                                                'tags' => [
                                                       ['id' => $tag2->id, 'pivot' => ['exclude' =>  true ]],
                                                ]])
        ->assertSuccessful()
        ->assertJsonFragment([
            'name' => $course1->version->name,
            'name' => $course3->version->name,
        ])
        ->assertJsonMissing([
            'name' => $course2->version->name,
        ]);
    }


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

        $tag = Tag::factory()->create();
        $course = Course::factory()->create();
        $course->addTag($tag);
        $course->refresh();
        $course->publish();

        $page = Page::factory()->create();
        $content_element = $this->createContentElement(CourseList::factory(), $page);
        $course_list = $content_element->content;
        $course_list->addTag($tag);
        $course_list->refresh();

        $this->assertTrue($course_list->courses->contains('id', $course->id));

        $this->assertNotNull($course->getCourseLists());
        $this->assertInstanceOf(Page::class, $course->getCourseLists()->first()->contentElement->contentables()->first()->pageable);
        //$this->assertEquals($page->id,

        $this->json('POST', route('courses.unlist', ['id' => $course->id]))
             ->assertSuccessful();

        $course->refresh();
        $this->assertTrue($course->version->unlisted);
        $course->publish();

        $this->assertFalse($course_list->courses->contains('id', $course->id));
    }

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

        $tag = Tag::factory()->create();
        $course = Course::factory()->create();
        $course->addTag($tag);
        $course->refresh();

        $page = Page::factory()->create();
        $content_element = $this->createContentElement(CourseList::factory(), $page);
        $course_list = $content_element->content;
        $course_list->addTag($tag);
        $course_list->refresh();

        $content_element2 = $this->createContentElement(PhotoBlock::factory(), $course);
        $course->publish();

        $content_element->delete();

        $this->assertNull($course_list->content_element);

        $course->flushCache();
        $version = $course->getDraftVersion();
        $this->assertFalse($version->unlisted);

        $this->json('POST', route('courses.unlist', ['id' => $course->id]))
             ->assertSuccessful();

        $course->refresh();
        $version = $course->getDraftVersion();

        $this->assertTrue($version->unlisted);
    }
}
