package com.iqser.red.service.peristence.v1.server.integration.tests;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.OffsetDateTime;
import java.util.Map;

import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.iqser.red.service.peristence.v1.server.integration.client.NotificationClient;
import com.iqser.red.service.peristence.v1.server.integration.client.NotificationPreferencesClient;
import com.iqser.red.service.peristence.v1.server.integration.service.UserProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.peristence.v1.server.integration.utils.MultithreadedTestRunner;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AddNotificationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.Notification;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationType;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Slf4j
public class NotificationTest extends AbstractPersistenceServerServiceTest {

    @Autowired
    private NotificationClient notificationClient;

    @Autowired
    private NotificationPreferencesClient notificationPreferencesClient;

    @Autowired
    private NotificationPersistenceService notificationPersistenceService;

    @Autowired
    private UserProvider userProvider;

    private final MultithreadedTestRunner multithreadedTestRunner = new MultithreadedTestRunner(2, 10);


    @Test
    public void testNotificationPreferences() {

        TenantContext.setTenantId("redaction");
        var userId = userProvider.getUserId();
        // initialize in app notifications
        var preferences = notificationPreferencesClient.getNotificationPreferences();

        notificationPersistenceService.insertNotification(AddNotificationRequest.builder().notificationType(NotificationType.ASSIGN_REVIEWER.name()).userId(userId).build());

        assertThat(notificationClient.getNotifications(true).getNotifications().size()).isEqualTo(1);

        // disable in app notifications
        preferences.setInAppNotificationsEnabled(false);
        notificationPreferencesClient.setNotificationPreferences(preferences);
        assertThat(notificationClient.getNotifications(true).getNotifications().size()).isEqualTo(0);

        // re-enable in app notifications
        preferences.setInAppNotificationsEnabled(true);
        notificationPreferencesClient.setNotificationPreferences(preferences);
        assertThat(notificationClient.getNotifications(true).getNotifications().size()).isEqualTo(0);

        notificationPersistenceService.insertNotification(AddNotificationRequest.builder().notificationType(NotificationType.ASSIGN_REVIEWER.name()).userId(userId).build());
        assertThat(notificationClient.getNotifications(true).getNotifications().size()).isEqualTo(1);

        preferences.getInAppNotifications().remove(NotificationType.ASSIGN_REVIEWER.name());
        notificationPreferencesClient.setNotificationPreferences(preferences);
        assertThat(notificationClient.getNotifications(true).getNotifications().size()).isEqualTo(0);

    }


    @Test
    public void testNotification() {
        TenantContext.setTenantId("redaction");
        var userId = userProvider.getUserId();

        notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
                .notificationType(NotificationType.ASSIGN_REVIEWER.name())
                .issuerId(userId)
                .userId(userId)
                .target(Map.of("test", "test"))
                .build());

        var notification = getNotification();

        assertThat(notification.getReadDate()).isNull();
        notificationClient.toggleNotificationRead(Lists.newArrayList(String.valueOf(notification.getId())), true);
        notification = getNotification();
        var hasNewNotifications = notificationClient.hasNewNotificationsSince(JSONPrimitive.of(OffsetDateTime.now().minusHours(1)));
        assertThat(hasNewNotifications.getValue()).isTrue();

        assertThat(notification.getReadDate()).isNotNull();

        assertThat(notification.getSeenDate()).isNull();
        notificationClient.toggleNotificationSeen(Lists.newArrayList(String.valueOf(notification.getId())), true);
        notification = getNotification();
        assertThat(notification.getSeenDate()).isNotNull();

        var currentNotifications = notificationClient.getNotifications(false);
        assertThat(currentNotifications.getNotifications()).isEmpty();

        notificationClient.toggleNotificationRead(Lists.newArrayList(String.valueOf(notification.getId())), false);
        notification = getNotification();
        assertThat(notification.getReadDate()).isNull();

        notificationClient.toggleNotificationSeen(Lists.newArrayList(String.valueOf(notification.getId())), false);
        notification = getNotification();
        assertThat(notification.getSeenDate()).isNull();

        notificationClient.delete(Lists.newArrayList(String.valueOf(notification.getId())));
        currentNotifications = notificationClient.getNotifications(true);
        assertThat(currentNotifications.getNotifications()).isEmpty();

    }


    private Notification getNotification() {

        var currentNotifications = notificationClient.getNotifications(true);
        assertThat(currentNotifications.getNotifications().size()).isEqualTo(1);

        return currentNotifications.getNotifications().iterator().next();
    }


    @Test
    @SneakyThrows
    public void testNotificationPreferencesConcurrent() {

        Runnable test = () -> notificationPreferencesClient.getNotificationPreferences();
        Runnable afterTest = () -> notificationPreferencesClient.deleteNotificationPreferences();
        var exceptions = multithreadedTestRunner.runMutlithreadedCollectingExceptions(test, afterTest);

        for (Exception ex : exceptions) {
            log.error("Exception during notification creation", ex);
        }

        assertThat(exceptions).isEmpty();
    }


    @Test
    @SneakyThrows
    public void testNotificationsConcurrent() {

        Runnable test = () -> notificationClient.getNotifications( false);
        Runnable afterTest = () -> notificationPreferencesClient.deleteNotificationPreferences();
        var exceptions = multithreadedTestRunner.runMutlithreadedCollectingExceptions(test, afterTest);

        for (Exception ex : exceptions) {
            log.error("Exception during notification creation", ex);
        }

        assertThat(exceptions).isEmpty();
    }

}
