<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Tests\TestCase;

use Google_Client;
use Google_Service_Calendar;

use Tests\Feature\ContentElementsTestTrait;
use Tests\Feature\ContentElementsSearchTestTrait;

use App\Models\Calendar;
use App\Models\GoogleCalendar;
use App\Models\Tag;
use App\Models\Page;
use App\Models\User;
use App\Models\Role;
use App\Models\ContentElement;

class CalendarTest extends TestCase
{
    use WithFaker;
    use ContentElementsTestTrait;
    use ContentElementsSearchTestTrait;

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

    public function test_a_calendar_can_be_loaded_from_the_google_api()
    {
        $calendarId = 'brentwood.ca_3pgnmrpebak3vo7npopp05gp1c@group.calendar.google.com';

        $client = new Google_Client();
        $client->setScopes(Google_Service_Calendar::CALENDAR_READONLY);
        $client->setAuthConfig(base_path('sa/calendar.json'));
        $client->setSubject('sa_developer@brentwood.ca');

        $service = new Google_Service_Calendar($client);

        $calendarId = 'brentwood.ca_3pgnmrpebak3vo7npopp05gp1c@group.calendar.google.com';
        //$calendarId = 'c_5msft04cj1bpva12fib38eknp0@group.calendar.google.com';
        $optParams = [
          'maxResults' => 10,
          'orderBy' => 'startTime',
          'singleEvents' => true,
          'timeMin' => date('c'),
        ];

        $results = $service->events->listEvents($calendarId, $optParams);
        $events = collect($results->getItems());

        $this->assertTrue($events->count() > 0);
    }

    public function test_a_calendar_can_be_created()
    {
        $tag1 = Tag::factory()->create();
        $tag2 = Tag::factory()->create();

        $input = $this->createContentElement(Calendar::factory())->toArray();
        $input['id'] = 0;
        $input['content_id'] = 0;
        $input['content_type'] = null;
        $input['content.id'] = 0;
        $input['content']['tags'] = [
            $tag1,
            $tag2,
        ];
        $google_id = Arr::get(GoogleCalendar::factory()->raw(), 'google_id');
        $calendar_name = $this->faker->firstName();
        $color = $this->faker->hexColor();
        $filter = Str::random();
        $input['content']['google_calendars'] = [
            [
                'id' => 0.1,
                'name' => $calendar_name,
                'google_id' => $google_id,
                'color' => $color,
                'filter' => $filter,
            ],
        ];

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

        $this->json('POST', route('content-elements.store'), [])
            ->assertStatus(401);

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

        $this->json('POST', route('content-elements.store'), [])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                 'pivot.contentable_id',
                 'pivot.contentable_type',
             ]);

        $this->json('POST', route('content-elements.store'), ['pivot' => ['contentable_id' => $page->id, 'contentable_type' => 'page']])
             ->assertStatus(403);

        $this->signInAdmin();

        $this->json('POST', route('content-elements.store'), ['pivot' => ['contentable_id' => $page->id, 'contentable_type' => 'page']])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                 'type',
             ]);

        $this->json('POST', route('content-elements.store'), ['type' => 'calendar', 'pivot' => ['contentable_id' => $page->id, 'contentable_type' => 'page']])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'pivot.sort_order',
                'pivot.unlisted',
                //'pivot.expandable',
                'pivot.guest',
                'pivot.no_margin',
                'pivot.randomize',
             ]);

        $this->withoutExceptionHandling();
        $this->json('POST', route('content-elements.store'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Calendar Saved',
             ]);

        $calendar = Calendar::all()->last();

        $this->assertNotNull($calendar->header);
        $this->assertNotNull($calendar->body);
        $this->assertNotNull($calendar->layout);
        $this->assertNotNull($calendar->count);

        $this->assertEquals(Arr::get($input, 'content.header'), $calendar->header);
        $this->assertEquals(Arr::get($input, 'content.body'), $calendar->body);
        $this->assertEquals(Arr::get($input, 'content.layout'), $calendar->layout);
        $this->assertEquals(Arr::get($input, 'content.count'), $calendar->count);
        //$this->assertEquals(Arr::get($input, 'content.calendar_id'), $calendar->calendar_id);
        $this->assertTrue($calendar->tags->contains('id', $tag1->id));
        $this->assertTrue($calendar->tags->contains('id', $tag2->id));

        $this->assertNotNull($calendar->googleCalendars);
        $this->assertEquals(1, $calendar->googleCalendars->count());
        $google_calendar = $calendar->googleCalendars->first();
        $this->assertNotNull($google_calendar->google_id);
        $this->assertEquals($google_id, $google_calendar->google_id);
        $this->assertEquals($color, $google_calendar->color);

        $this->assertNotNull($google_calendar->name);
        $this->assertEquals($calendar_name, $google_calendar->name);
        $this->assertEquals($filter, $google_calendar->filter);
    }

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

        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);
        $calendar = $content_element->content;
        $google_calendar = $calendar->googleCalendars()->first();
        $events = $google_calendar->getCalendarEvents();

        $this->assertNotNull($events->first()->color);
        $this->assertEquals($google_calendar->color, $events->first()->color);

        $this->assertInstanceOf(Calendar::class, $calendar);

        $input = $content_element->toArray();

        $header = Arr::get(Calendar::factory()->raw(), 'header');
        $body = Arr::get(Calendar::factory()->raw(), 'body');
        $color = $this->faker->hexColor();
        $name = Arr::get(GoogleCalendar::factory()->raw(), 'name');

        $input['content']['header'] = $header;
        $input['content']['body'] = $body;
        $input['content']['google_calendars'][0]['name'] = $name;
        $input['content']['google_calendars'][0]['color'] = $color;
        $input['pivot'] = $this->getContentableArray($page);

        $this->withoutExceptionHandling();
        $this->json('POST', route('content-elements.update', ['id' => $content_element->id]), $input)
            ->assertSuccessful()
            ->assertJsonFragment([
                'success' => 'Calendar Saved',
            ]);

        $content_element->refresh();
        $calendar->refresh();
        $google_calendar->refresh();

        $this->assertEquals($header, $calendar->header);
        $this->assertEquals($body, $calendar->body);
        $this->assertEquals($name, $google_calendar->name);
        $this->assertEquals($color, $google_calendar->color);
    }

    public function test_a_calendar_can_load_its_events()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);
        $page->publish();
        $calendar = $content_element->content;

        $google_calendar = $calendar->googleCalendars->first();
        $this->assertInstanceOf(GoogleCalendar::class, $google_calendar);
        $event = $google_calendar->getCalendarEvents()->first();

        $this->assertNotNull($event);
        $this->assertNotNull($event->title);

        $this->json('POST', route('calendars.events'), [])
             ->assertStatus(422)
            ->assertJsonValidationErrors([
                'uuid',
                'contentable_id',
                'contentable_type',
            ]);

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
            'paginate' => 1,
        ];

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful();

        $user = User::factory()->create();
        $role = Role::factory()->create();
        $content_element->createPermission('view', $role);

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

        $this->signIn($user);

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

        $user->addRole($role);

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

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

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $event->title,
             ]);

        $second_calendar = GoogleCalendar::factory()->create([
            'calendar_id' => $calendar->id,
            'google_id' => 'c_5msft04cj1bpva12fib38eknp0@group.calendar.google.com',
        ]);

        $calendar->refresh();

        $this->assertEquals(2, $calendar->googleCalendars()->count());

        $input['google_calendars'] = [
            $second_calendar->toArray(),
        ];

        $event2 = $second_calendar->getCalendarEvents()->first();

        $this->assertNotNull($event2);
        $this->assertNotNull($event2->title);

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $event2->title,
             ])
             ->assertJsonMissing([
                'title' => $event->title,
             ]);
    }

    public function test_a_calendar_can_load_all_of_its_events_not_in_a_paginator()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);
        $page->publish();
        $calendar = $content_element->content;

        $google_calendar = $calendar->googleCalendars->first();
        $this->assertInstanceOf(GoogleCalendar::class, $google_calendar);
        $event1 = $google_calendar->getCalendarEvents()->first();
        $event2 = $google_calendar->getCalendarEvents()->last();

        $this->assertNotNull($event1);
        $this->assertNotNull($event1->title);
        $this->assertNotNull($event2);
        $this->assertNotNull($event2->title);

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
        ];

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                 'title' => $event1->title,
                 'title' => $event2->title,
             ])
             ->assertJsonMissing([
                'current_page' => 1,
             ]);
    }

    public function test_a_google_calendar_can_be_remove()
    {
        $this->signInAdmin();
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()->count(2)), $page);

        $calendar = $content_element->content;
        $this->assertInstanceOf(Calendar::class, $calendar);

        $this->assertEquals(2, $calendar->googleCalendars->count());

        $google_calendar1 = $calendar->googleCalendars->first();
        $google_calendar2 = $calendar->googleCalendars->last();

        $input = $content_element->toArray();

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

        $this->json('POST', route('content-elements.update', ['id' => $content_element->id]), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Calendar Saved',
             ]);

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

        $this->assertEquals(1, $calendar->googleCalendars->count());
        $this->assertEquals($google_calendar1->id, $calendar->googleCalendars->first()->id);
    }

    public function test_draft_versions_dont_show_events_in_the_frontend()
    {
        $this->signInAdmin();
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);

        $google_id = 'c_5msft04cj1bpva12fib38eknp0@group.calendar.google.com';
        $google_id2 = 'en.canadian#holiday@group.v.calendar.google.com';

        $calendar = $content_element->content;
        $this->assertInstanceOf(Calendar::class, $calendar);

        $google_calendar = $calendar->googleCalendars->first();
        $google_calendar->google_id = $google_id;
        $google_calendar->save();

        $page->publish();

        $google_calendar = $calendar->googleCalendars->first();
        $this->assertInstanceOf(GoogleCalendar::class, $google_calendar);
        $event = $google_calendar->getCalendarEvents()->first();

        $this->assertEquals(1, $content_element->content->googleCalendars()->count());

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
            'paginate' => 1,
            'paginate_count' => $google_calendar->getCalendarEvents()->count(),
        ];

        auth()->logout();

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $event->title,
             ]);

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

        $preview_url = $page->preview_url;
        $this->assertNotNull($preview_url);

        $calendar_input = $content_element->toArray();

        $new_calendar = [
            'id' => 0,
            'google_id' => $google_id2,
        ];

        $calendar_input['content']['google_calendars'] = [
            $google_calendar->toArray(),
            $new_calendar,
        ];

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

        $this->json('POST', route('content-elements.update', ['id' => $content_element->id]), $calendar_input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Calendar Saved',
             ]);

        $new_content_element = ContentElement::all()->last();
        $new_calendar = $new_content_element->content->googleCalendars()->get()->last();
        $this->assertEquals($google_id2, $new_calendar->google_id);
        $this->assertTrue($new_calendar->getCalendarEvents()->count() > 0);
        $event2 = $new_calendar->getCalendarEvents()->first();

        $input['paginate_count'] = $google_calendar->getCalendarEvents()->count() + $new_calendar->getCalendarEvents()->count();

        $this->assertEquals($content_element->uuid, $new_content_element->uuid);

        $this->assertEquals(2, $new_content_element->content->googleCalendars()->count());

        $content_element->refresh();

        $this->assertEquals(1, $content_element->content->googleCalendars()->count());

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

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $event->title,
             ])
             ->assertJsonMissing([
                'title' => $event2->title,
             ]);

        $this->get($preview_url)
             ->assertSuccessful()
             ->assertSessionHas('preview_version_id', $page->getDraftVersion()->id);

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $event->title,
                'title' => $event2->title,
             ]);

        $page->publish();

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'title' => $event->title,
                'title' => $event2->title,
             ]);
    }

    public function test_a_calendars_items_can_be_searched()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);
        $page->publish();
        $calendar = $content_element->content;

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

        $page->publish();

        $calendar_events = $calendar->getCalendarEvents()->random(2);

        $find_event = $calendar_events->first();
        $missing_event = $calendar_events->last();

        $terms = Str::lower(collect(explode(' ', $find_event->title))->random(2)->implode(' '));

        $this->assertNotNull($terms);

        $this->disableEditing();
        auth()->logout();

        $this->assertFalse(auth()->check());

        $this->json('POST', route('calendars.events'))
             ->assertJsonValidationErrors([
                 'uuid',
                 'contentable_id',
                 'contentable_type',
             ]);

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
            'terms' => $terms,
            'paginate' => 1,
        ];

        $this->json('POST', route('calendars.events.search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'id' => $find_event->id,
             ])
             ->assertJsonMissing([
                'id' => $missing_event->id,
             ]);

        $terms = substr($find_event->title, 0, 4);

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
            'terms' => $terms,
        ];

        $this->json('POST', route('calendars.events.search'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'id' => $find_event->id,
             ])
             ->assertJsonMissing([
                'id' => $missing_event->id,
             ]);
    }

    public function test_a_calendar_can_request_a_start_and_an_end_date()
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);
        $page->publish();
        $calendar = $content_element->content;

        $google_calendar = $calendar->googleCalendars->first();
        $this->assertInstanceOf(GoogleCalendar::class, $google_calendar);
        $event1 = $google_calendar->getCalendarEvents()->first();
        $event2 = $google_calendar->getCalendarEvents()->random();
        $event3 = $google_calendar->getCalendarEvents()->last();

        $this->assertNotNull($event1);
        $this->assertNotNull($event1->title);
        $this->assertNotNull($event2);
        $this->assertNotNull($event2->title);
        $this->assertNotNull($event3);
        $this->assertNotNull($event3->title);

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
            'start_date' => $event1->start_date,
            'end_date' => $event2->end_date,
            'paginate_count' => $google_calendar->getCalendarEvents()->count(),
        ];

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                 'title' => $event1->title,
                 'title' => $event2->title,
             ])
             ->assertJsonMissing([
                'title' => $event3->title,
             ]);

        $input['start_date'] = $event2->start_date;
        $input['end_date'] = $event3->end_date;

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                 'title' => $event2->title,
                 'title' => $event3->title,
             ])
             ->assertJsonMissing([
                'title' => $event1->title,
             ]);
    }

    public function test_a_calendar_can_find_events_pages(): void
    {
        $page = Page::factory()->create();
        $content_element = $this->createContentElement(Calendar::factory()->has(GoogleCalendar::factory()), $page);
        $page->publish();
        $calendar = $content_element->content;

        $google_calendar = $calendar->googleCalendars->first();
        $this->assertInstanceOf(GoogleCalendar::class, $google_calendar);
        $events = $google_calendar->getCalendarEvents();

        $upcoming_event = $events->where(function ($event) {
            return Str::contains($event->description, '.brentwood.ca/');
        })->last();

        $non_event = $events->firstWhere(function ($event) {
            return !Str::contains($event->description, '.brentwood.ca/');
        });

        $this->assertNotNull($upcoming_event);

        $this->assertNotNull($upcoming_event->page);
        $this->assertInstanceOf(Page::class, $upcoming_event->page);

        $input = [
            'uuid' => $content_element->uuid,
            'contentable_id' => $page->id,
            'contentable_type' => $page->type,
            'paginate' => 1,
            'pages' => true,
        ];

        $this->json('POST', route('calendars.events'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'full_slug' => $upcoming_event->page->full_slug,
             ])
             ->assertJsonMissing([
                'id' => $non_event->id,
             ]);
    }
}
