package ru.yandex.webmaster3.worker.notifications.services;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.core.util.concurrent.graph.BlockingBatchConsumer;
import ru.yandex.webmaster3.core.util.concurrent.graph.GraphOutQueue;
import ru.yandex.webmaster3.storage.notifications.NotificationChannel;
import ru.yandex.webmaster3.storage.user.dao.UserNotificationChannelsYDao;
import ru.yandex.webmaster3.storage.user.dao.UserNotificationHostSettingsYDao;
import ru.yandex.webmaster3.storage.user.notification.HostNotificationMode;
import ru.yandex.webmaster3.storage.user.notification.HostNotificationSetting;
import ru.yandex.webmaster3.storage.user.notification.NotificationType;
import ru.yandex.webmaster3.worker.notifications.info.UserHostsChannelsInfo;
import ru.yandex.webmaster3.worker.notifications.info.UserHostsInfo;

/**
 * Created by ifilippov5 on 29.08.17.
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ChannelsResolver {
    private static final RetryUtils.RetryPolicy RETRY_POLICY = RetryUtils.linearBackoff(10, Duration.standardMinutes(2));

    private final UserNotificationChannelsYDao userNotificationChannelsYDao;
    private final UserNotificationHostSettingsYDao userNotificationHostSettingsYDao;

    public BlockingBatchConsumer<UserHostsInfo> resolve(GraphOutQueue<UserHostsChannelsInfo> out,
                                                        GraphOutQueue<UserHostsChannelsInfo> clarifyEmailsQ,
                                                        Set<NotificationType> notificationTypes) {
        return batch -> {
            Set<Long> userIds = batch.stream().map(UserHostsInfo::getUserId).collect(Collectors.toSet());
            Map<Long, Map<NotificationType, Set<NotificationChannel>>> user2Channels =
                    RetryUtils.query(RETRY_POLICY, () -> userNotificationChannelsYDao.getChannelsInfo(userIds));
            Map<Long, List<HostNotificationSetting>> user2HostNotificationSettings =
                    RetryUtils.query(RETRY_POLICY, () -> userNotificationHostSettingsYDao.listSettingsForUsers(userIds));
            for (UserHostsInfo userHostsInfo : batch) {
                Map<NotificationType, Set<NotificationChannel>> type2Channels = user2Channels.get(userHostsInfo.getUserId());
                if (type2Channels == null) {
                    type2Channels = Collections.emptyMap();
                }
                boolean needEmail = false;
                Map<WebmasterHostId, Map<NotificationType, Set<NotificationChannel>>> host2Channels = new HashMap<>();
                for (NotificationType notificationType : notificationTypes) {
                    Set<NotificationChannel> defaultChannels = type2Channels.getOrDefault(notificationType, notificationType.getDefaultChannels());
                    Map<WebmasterHostId, Map<NotificationChannel, HostNotificationMode>> host2Settings =
                            user2HostNotificationSettings.getOrDefault(userHostsInfo.getUserId(), Collections.emptyList())
                                    .stream()
                                    .filter(s -> s.getType() == notificationType)
                                    .collect(Collectors.groupingBy(
                                            HostNotificationSetting::getHostId,
                                            Collectors.toMap(HostNotificationSetting::getChannel, HostNotificationSetting::getMode)
                                    ));
                    for (WebmasterHostId hostId : userHostsInfo.getHosts()) {
                        Map<NotificationChannel, HostNotificationMode> channel2Mode = host2Settings.getOrDefault(hostId, Collections.emptyMap());
                        for (NotificationChannel channel : NotificationChannel.values()) {
                            HostNotificationMode hostMode = channel2Mode.getOrDefault(channel, HostNotificationMode.DEFAULT);
                            boolean needChannel;
                            switch (hostMode) {
                                case ENABLED:
                                    needChannel = true;
                                    break;
                                case DISABLED:
                                    needChannel = false;
                                    break;
                                case DEFAULT:
                                    needChannel = defaultChannels.contains(channel);
                                    break;
                                default:
                                    throw new RuntimeException("Unknown host notification mode " + hostMode);
                            }
                            if (needChannel) {
                                if (channel == NotificationChannel.EMAIL) {
                                    needEmail = true;
                                }
                                host2Channels.computeIfAbsent(hostId, ign -> new HashMap<>())
                                        .computeIfAbsent(notificationType, ign -> EnumSet.noneOf(NotificationChannel.class))
                                        .add(channel);
                            }
                        }
                    }
                }
                if (!host2Channels.isEmpty()) {
                    UserHostsChannelsInfo msg = new UserHostsChannelsInfo(userHostsInfo.getUserId(), host2Channels, null, null);
                    if (needEmail) {
                        clarifyEmailsQ.put(msg);
                    } else {
                        out.put(msg);
                    }
                }
            }
        };
    }

}
