<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

use App\Models\Livestream;
use App\Models\Inquiry;
use App\Models\User;
use App\Models\Chat;

use App\Events\ChatMessageCreated;
use App\Events\WhisperCreated;
use App\Events\ChatMessageSelected;

class ChatTest extends TestCase
{
    use WithFaker;

    public function test_a_new_message_can_be_sent_to_chat()
    {
        $message = $this->faker->sentence;
        $livestream = Livestream::factory()->create([
            'unlisted' => false,
        ]);
        $inquiry = Inquiry::factory()->create();
        $inquiry->saveLivestreams(['livestream' => $livestream]);

        $user = $inquiry->user;
        $this->assertInstanceOf(User::class, $user);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
        ];

        $this->json('POST', route('chat.send-message'), [])
             ->assertStatus(401);

        $this->signIn($user);

        $this->json('POST', route('chat.send-message'), ['room' => 'livestream.'.$livestream->id])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'message',
             ]);

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $user->name,
             ]);

        Event::assertDispatched(function (ChatMessageCreated $event) use ($message) {
            return $event->chat->message === $message;
        });
    }

    public function test_a_chat_can_be_deleted()
    {
        $chat = Chat::factory()->create();

        $this->json('POST', route('chat.delete', ['id' => $chat->id]))
             ->assertStatus(401);

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

        $this->json('POST', route('chat.delete', ['id' => $chat->id]))
             ->assertStatus(403);

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

        $this->json('POST', route('chat.delete', ['id' => $chat->id]))
             ->assertStatus(403);

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

        $this->json('POST', route('chat.delete', ['id' => $chat->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Message Deleted',
             ]);

        $this->assertFalse(Chat::all()->contains('id', $chat->id));

        $chat2 = Chat::factory()->create();

        $user->removeRole('livestreams-manager');
        $user->refresh();

        $this->json('POST', route('chat.delete', ['id' => $chat2->id]))
             ->assertStatus(403);

        $object = $chat->getObject();
        $object->createPermission('moderate', $user);
        $user->refresh();

        $this->json('POST', route('chat.delete', ['id' => $chat2->id]))
             ->assertSuccessful()
             ->assertJsonFragment([
                'success' => 'Message Deleted',
             ]);

        $this->assertFalse(Chat::all()->contains('id', $chat2->id));
    }

    public function test_chat_can_be_loaded_for_a_specific_room()
    {
        $chat = Chat::factory()->create();
        $old_chat = Chat::factory()->create([
            'created_at' => now()->subMinutes(6),
            'updated_at' => now()->subMinutes(6),
        ]);

        $this->assertTrue($old_chat->created_at->isPast());

        $other_chat = Chat::factory()->create([
            'room' => 'fooabar.1',
        ]);

        $this->json('POST', route('chat.load'))
            ->assertStatus(401);

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

        $this->json('POST', route('chat.load'))
             ->assertStatus(422)
            ->assertJsonValidationErrors([
                'room',
            ]);

        $this->signInAdmin();

        $this->json('POST', route('chat.load'), ['room' => $chat->room])
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $chat->message,
             ])
             ->assertJsonMissing([
                'message' => $old_chat->message,
                'message' => $other_chat->message,
             ]);
    }

    public function test_a_chat_room_can_be_viewed()
    {
        $livestream = Livestream::factory()->create();
        $room = $livestream->chat_room;

        $this->assertNotNull($room);

        $this->get(route('chat.view', ['room' => $room]))
            ->assertRedirect('/login');

        $this->signInAdmin();

        $this->get(route('chat.view', ['room' => $room]))
             ->assertSuccessful()
             ->assertViewHas('room', $room);
    }

    public function test_a_message_can_be_whispered()
    {
        $message = $this->faker->sentence;
        $livestream = Livestream::factory()->create();
        $inquiry = Inquiry::factory()->create();
        $inquiry->saveLivestreams(['livestream' => $livestream]);

        $user = $inquiry->user;
        $this->assertInstanceOf(User::class, $user);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
            'whisper_id' => $user->id,
        ];

        $this->json('POST', route('chat.send-message'), [])
             ->assertStatus(401);

        $this->signInAdmin();

        $this->json('POST', route('chat.send-message'), ['room' => 'livestream.'.$livestream->id])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'message',
             ]);

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
             ]);

        Event::assertDispatched(function (WhisperCreated $event) use ($message, $user) {
            return $event->chat->message === $message && $event->user->id === $user->id;
        });

        $this->assertTrue($user->whispers()->get()->contains('message', $message));
    }

    public function test_a_whisper_is_not_loaded_into_other_peoples_chat()
    {
        $message = $this->faker->sentence;
        $whisper = $this->faker->sentence;
        $livestream = Livestream::factory()->create();
        $inquiry = Inquiry::factory()->create();
        $inquiry2 = Inquiry::factory()->create();
        $inquiry->saveLivestreams(['livestream' => $livestream]);
        $inquiry2->saveLivestreams(['livestream' => $livestream]);
        $room = 'livestream.'.$livestream->id;

        $user = $inquiry->user;
        $this->assertInstanceOf(User::class, $user);

        $input = [
            'room' => $room,
            'message' => $message,
        ];

        $whisper_input = [
            'room' => $room,
            'message' => $whisper,
            'whisper_id' => $user->id,
        ];

        $admin = User::find(1);
        $this->assertTrue($admin->hasRole('admin'));
        $this->signIn($admin);

        Event::fake();

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
             ]);


        $this->json('POST', route('chat.send-message'), $whisper_input)
        ->assertSuccessful()
        ->assertJsonFragment([
            'message' => $whisper,
        ]);

        $this->assertTrue($user->whispers()->get()->contains('message', $whisper));

        $global_chat = Chat::where('message', $message)->first();
        $this->assertInstanceOf(Chat::class, $global_chat);
        $whisper_chat = Chat::where('message', $whisper)->first();
        $this->assertInstanceOf(Chat::class, $whisper_chat);

        Event::assertDispatched(function (WhisperCreated $event) use ($whisper, $user) {
            return $event->chat->message === $whisper && $event->user->id === $user->id;
        });

        $this->signIn($user);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonFragment([
                 'id' => $whisper_chat->id,
                 'message' => $whisper_chat->message,
             ]);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonFragment([
                 'id' => $global_chat->id,
                 'message' => $global_chat->message,
             ]);

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

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonMissing([
                 'id' => $whisper_chat->id,
                 'message' => $whisper_chat->message,
             ]);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonFragment([
                 'id' => $global_chat->id,
                 'message' => $global_chat->message,
             ]);

        $this->signIn($admin);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonFragment([
                 'id' => $whisper_chat->id,
                 'message' => $whisper_chat->message,
             ]);

        $this->json('POST', route('chat.load'), ['room' => $room])
             ->assertJsonMissing([
                 'email' => $global_chat->user->email,
            ])
             ->assertJsonFragment([
                 'id' => $global_chat->id,
                 'message' => $global_chat->message,
             ]);
    }

    public function test_in_private_mode_users_always_whisper_mods()
    {
        $mod = User::factory()->create();
        $message = $this->faker->sentence;
        $livestream = Livestream::factory()->create([
            'chat_mode' => 'private',
            'unlisted' => false,
        ]);
        $livestream->createPermission('moderate', $mod);
        $livestream->refresh();

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

        $this->signIn($user);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
        ];

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
             ]);

        Event::assertDispatched(function (WhisperCreated $event) use ($message, $user, $mod) {
            return $event->chat->message === $message && $event->user->id === $mod->id;
        });

        $this->assertTrue($mod->whispers()->get()->contains('message', $message));
    }

    public function test_in_private_mode_moderators_can_send_messages_to_all_users()
    {
        $user = User::factory()->create();
        $livestream = Livestream::factory()->create([
            'chat_mode' => 'private',
        ]);

        $livestream->createPermission('moderate', $user);

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

        $this->signIn($user);

        $this->assertTrue($user->can('chat', $livestream));

        $message = $this->faker->sentence;

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
        ];

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $user->name,
             ]);

        Event::assertDispatched(function (ChatMessageCreated $event) use ($message) {
            return $event->chat->message === $message;
        });
    }

    public function test_in_private_mode_mods_can_send_whispers_to_users()
    {
        $mod = User::factory()->create();
        $message = $this->faker->sentence;
        $livestream = Livestream::factory()->create([
            'chat_mode' => 'private',
            'unlisted' => false,
        ]);
        $livestream->createPermission('moderate', $mod);
        $livestream->refresh();

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

        $this->signIn($mod);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
            'whisper_id' => $user->id,
        ];

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
             ]);

        Event::assertNotDispatched(function (ChatMessageCreated $event) use ($message) {
            return $event->chat->message === $message;
        });

        Event::assertDispatched(function (WhisperCreated $event) use ($message, $user, $mod) {
            return $event->chat->message === $message && $event->user->id === $user->id;
        });

        $this->assertTrue($user->whispers()->get()->contains('message', $message));
    }

    public function test_an_unlisted_event_requires_users_to_register_to_chat()
    {
        $message = $this->faker->sentence;
        $livestream = Livestream::factory()->create([
            'unlisted' => true,
        ]);
        $inquiry = Inquiry::factory()->create();
        $inquiry->saveLivestreams(['livestream' => $livestream]);

        $user = $inquiry->user;
        $this->assertInstanceOf(User::class, $user);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
        ];

        $this->json('POST', route('chat.send-message'), [])
             ->assertStatus(401);

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

        $this->json('POST', route('chat.send-message'), [])
             ->assertStatus(403);

        $this->signIn($user);

        $this->json('POST', route('chat.send-message'), ['room' => 'livestream.'.$livestream->id])
             ->assertStatus(422)
             ->assertJsonValidationErrors([
                'message',
             ]);

        Event::fake();

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $user->name,
             ]);

        Event::assertDispatched(function (ChatMessageCreated $event) use ($message) {
            return $event->chat->message === $message;
        });
    }

    public function test_a_chat_message_can_be_selected()
    {
        $message = $this->faker->sentence;
        $livestream = Livestream::factory()->create([
            'unlisted' => false,
        ]);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
        ];

        $user = User::find(1);
        $this->assertTrue($user->hasRole('admin'));
        $this->signIn($user);

        Event::fake();

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $user->name,
             ]);

        Event::assertDispatched(function (ChatMessageCreated $event) use ($message) {
            return $event->chat->message === $message;
        });

        auth()->logout();

        $chat = Chat::all()->last();

        $this->json('POST', route('chat.select', ['id' => $chat->id]))
            ->assertStatus(401);

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

        $this->json('POST', route('chat.select', ['id' => $chat->id]))
            ->assertStatus(403);

        $mod->createPermission('moderate', $livestream);

        $this->assertNotNull($chat->selected);
        $this->assertFalse($chat->selected);

        $this->json('POST', route('chat.select', ['id' => $chat->id]))
             ->assertSuccessful();

        $chat->refresh();

        $this->assertTrue($chat->selected);

        Event::assertDispatched(function (ChatMessageSelected $event) use ($chat) {
            return $event->chat->id === $chat->id;
        });

        $this->json('POST', route('chat.select', ['id' => $chat->id]))
             ->assertSuccessful();

        $chat->refresh();

        $this->assertFalse($chat->selected);
    }

    public function test_if_you_are_a_mod_in_private_chat_you_should_be_able_to_see_all_whispers()
    {
        $public_message = $this->faker->sentence;
        $message = $this->faker->sentence;

        $mod1 = User::factory()->create();
        $mod2 = User::factory()->create();

        $livestream = Livestream::factory()->create([
            'chat_mode' => 'private',
            'unlisted' => false,
        ]);

        $livestream->createPermission('moderate', $mod1);
        $livestream->createPermission('moderate', $mod2);

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

        $this->signIn($user);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
        ];

        $this->withoutExceptionHandling();
        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $user->name,
             ]);

        $this->signIn($mod1);


        // sending a message to all shouldnt whisper
        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $public_message,
        ];

        Event::fake();

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $public_message,
                'name' => $mod1->name,
             ]);

        Event::assertNotDispatched(function (WhisperCreated $event) use ($public_message) {
            return $event->chat->message === $public_message;
        });

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
            'whisper_id' => $user->id,
        ];

        Event::fake();

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $user->name,
             ]);

        $this->assertTrue($user->whispers()->get()->contains('message', $message));
        $this->assertTrue($mod1->chats()->get()->contains('message', $message));
        $this->assertTrue($mod2->whispers()->get()->contains('message', $message));

        Event::assertDispatched(function (WhisperCreated $event) use ($message, $user) {
            return $event->chat->message === $message && $event->user->id === $user->id;
        });

        /* We send to other so this isnt dispatched to the sender
        Event::assertDispatched(function (WhisperCreated $event) use ($message, $mod1) {
            return $event->chat->message === $message && $event->user->id === $mod1->id;
        });
         */

        Event::assertDispatched(function (WhisperCreated $event) use ($message, $mod2) {
            return $event->chat->message === $message && $event->user->id === $mod2->id;
        });
    }

    public function test_a_chat_can_have_a_replying_to_when_whispering_in_private_mode()
    {
        $message = $this->faker->sentence;

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

        $livestream = Livestream::factory()->create([
            'chat_mode' => 'private',
            'unlisted' => false,
        ]);

        $livestream->createPermission('moderate', $mod);

        $chat = Chat::factory()->create([
            'room' => 'livestream.'.$livestream->id,
            'user_id' => $user->id,
        ]);

        $this->signIn($mod);

        $input = [
            'room' => 'livestream.'.$livestream->id,
            'message' => $message,
            'whisper_id' => $user->id,
            'reply_id' => $chat->id,
        ];

        Event::fake();

        $this->json('POST', route('chat.send-message'), $input)
             ->assertSuccessful()
             ->assertJsonFragment([
                'message' => $message,
                'name' => $chat->user->name,
             ]);

        $this->assertTrue($user->whispers()->get()->contains('message', $message));
        $this->assertTrue($mod->chats()->get()->contains('message', $message));

        $reply = Chat::all()->last();

        $this->assertEquals($chat->id, $reply->reply->id);
        $this->assertEquals($chat->message, $reply->reply->message);
    }
}
