<?php

use humhub\modules\live\models\Live;
use humhub\modules\mail\models\forms\CreateMessage;
use humhub\modules\mail\models\forms\InviteParticipantForm;
use humhub\modules\mail\models\forms\ReplyForm;
use humhub\modules\mail\notifications\ConversationNotification;
use humhub\modules\mail\notifications\MailNotification;
use humhub\modules\notification\components\BaseNotification;
use humhub\modules\notification\models\forms\NotificationSettings;
use humhub\modules\notification\targets\MailTarget;
use tests\codeception\_support\HumHubDbTestCase;
use humhub\modules\user\models\User;
use yii\helpers\ArrayHelper;

class NotificationTest extends HumHubDbTestCase
{
    public function _before()
    {
        // Required in HumHub < 1.6.3
        Live::deleteAll();
        parent::_before(); // TODO: Change the autogenerated stub
    }

    /**
     * @param $title
     * @param $message
     * @param null $users
     * @param array $tags
     * @return CreateMessage
     * @throws Throwable
     */
    private function createMessage($title, $message, $users = null, $tags = []): CreateMessage
    {
        $this->becomeUser('User1');

        $message = new CreateMessage([
            'title' => $title,
            'message' => $message,
            'recipient' => $users,
            'tags' => $tags,
        ]);

        $this->assertTrue($message->save());

        return $message;
    }

    /**
     * @param CreateMessage $message
     * @param string $content
     * @return ReplyForm
     */
    private function createReply(CreateMessage $message, string $content): ReplyForm
    {
        $replyForm = new ReplyForm([
            'model' => $message->messageInstance,
            'message' => $content,
        ]);

        $this->assertTrue($replyForm->save());

        return $replyForm;
    }

    public function testNotificationsAreSentWhenCreatingAMessage()
    {
        $user2 = User::findOne(['id' => 3]);

        $this->createMessage('First', 'First', [$user2->guid]);

        // Check mail should only be sent to
        $this->assertMailSent(1);
        $this->assertEqualsLastEmailSubject('New conversation from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);
        $test = Live::find()->all();
        $this->assertCount(1, Live::find()->where(['contentcontainer_id' => $user2->contentcontainer_id])->all());
    }

    public function testNotificationsAreSentAllRecepients()
    {
        $user2 = User::findOne(['id' => 3]);
        $user3 = User::findOne(['id' => 4]);


        $this->createMessage('First', 'First', [$user2->guid, $user3->guid]);

        // Check mail should only be sent to
        $this->assertMailSent(2);
        $this->assertEqualsLastEmailSubject('New conversation from Peter Tester');
        $this->assertEqualsLastEmailTo($user3->email);

        $test =  Live::find(['contentcontainer_id' => $user2->contentcontainer_id])->all();
        $test2 = unserialize($test[0]->serialized_data);
        $test3 = unserialize($test[1]->serialized_data);
        $this->assertCount(1, Live::find()->where(['contentcontainer_id' => $user2->contentcontainer_id])->all());
        $this->assertCount(1, Live::find()->where(['contentcontainer_id' => $user3->contentcontainer_id])->all());
    }

    public function testNotificationsAreSentAfterAddingParticipant()
    {
        $user2 = User::findOne(['id' => 3]);
        $message = $this->createMessage('First', 'First', [$user2->guid]);
        $this->assertSentEmail(1);
        $this->assertEqualsLastEmailSubject('New conversation from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);

        sleep(1); // Wait for the queue to finish sending emails

        $user3 = User::findOne(['id' => 4]);
        $inviteForm = new InviteParticipantForm(['message' => $message->messageInstance, 'recipients' => [$user3->guid]]);
        $inviteForm->save();

        $this->assertSentEmail(3);
        $this->assertLastEmailsContainsSubject('Andreas Tester joined the conversation.'); // user3 (id=4)
    }

    public function testAllNotificationsAreDisabled()
    {
        $user2 = User::findOne(['id' => 3]);
        $this->setNotifications($user2, [
            ConversationNotification::class => false,
            MailNotification::class => false,
        ]);

        $this->createMessage('New conversation title', 'New conversation message', [$user2->guid]);

        // No notifications because all disabled
        $this->assertMailSent();
    }

    public function testOnlyNewConversationNotificationIsEnabled()
    {
        $user2 = User::findOne(['id' => 3]);
        $this->setNotifications($user2, [
            ConversationNotification::class => true,
            MailNotification::class => false,
        ]);

        $message = $this->createMessage('New conversation title', 'New conversation message', [$user2->guid]);

        // Notification about new conversation is received
        $this->assertMailSent(1);
        $this->assertEqualsLastEmailSubject('New conversation from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);

        $this->createReply($message, 'Reply message');

        // No new notification about new message
        $this->assertMailSent(1);
    }

    public function testOnlyNewMessageNotificationIsEnabled()
    {
        $user2 = User::findOne(['id' => 3]);
        $this->setNotifications($user2, [
            ConversationNotification::class => false,
            MailNotification::class => true,
        ]);

        $message = $this->createMessage('New conversation title', 'New conversation message', [$user2->guid]);

        // Instead of notification about new Conversation the user received a notification of new Message
        $this->assertMailSent(1);
        $this->assertEqualsLastEmailSubject('New message from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);

        $this->createReply($message, 'Reply message');

        // New notification about new message is received
        $this->assertMailSent(2);
        $this->assertEqualsLastEmailSubject('New message from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);
    }

    public function testAllNotificationsAreEnabled()
    {
        $user2 = User::findOne(['id' => 3]);
        $this->setNotifications($user2, [
            ConversationNotification::class => true,
            MailNotification::class => true,
        ]);

        $message = $this->createMessage('New conversation title', 'New conversation message', [$user2->guid]);

        // Notification about new conversation is received
        $this->assertMailSent(1);
        $this->assertEqualsLastEmailSubject('New conversation from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);

        $this->createReply($message, 'Reply message');

        // New notification about new message is received
        $this->assertMailSent(2);
        $this->assertEqualsLastEmailSubject('New message from Peter Tester');
        $this->assertEqualsLastEmailTo($user2->email);
    }

    public function assertEqualsLastEmailTo($email, $strict = true)
    {
        $this->assertArrayHasKey($email, $this->getYiiModule()->grabLastSentEmail()->to);
    }

    public function assertLastEmailsContainsSubject($subject)
    {
        $this->assertContains(
            $subject,
            ArrayHelper::getColumn($this->getYiiModule()->grabSentEmails(), function ($message) {
                return $message->getSubject();
            }),
        );
    }

    private function setNotifications(User $user, array $config)
    {
        $this->becomeUser($user->username);

        $mailTarget = Yii::$app->notification->getTarget(MailTarget::class);

        $settings = [];
        foreach ($config as $notificationClass => $state) {
            /* @var BaseNotification $notification */
            $notification = new $notificationClass();
            $settings[$mailTarget->getSettingKey($notification->getCategory())] = $state;
        }

        $settingForm = new NotificationSettings([
            'user' => $user,
            'settings' => $settings,
        ]);

        $settingForm->save();
    }
}
