package ru.yandex.webmaster3.storage.checklist.service;

import java.util.EnumMap;
import java.util.Map;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.checklist.data.SiteProblemSeverityEnum;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemState;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemTypeEnum;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.storage.checklist.dao.RecentlySentChecklistNotificationsYDao;
import ru.yandex.webmaster3.storage.checklist.data.AbstractProblemInfo;
import ru.yandex.webmaster3.storage.checklist.data.ProblemSignal;
import ru.yandex.webmaster3.storage.checklist.data.RealTimeSiteProblemInfo;
import ru.yandex.webmaster3.storage.checklist.data.RecentlySentProblemNotification;
import ru.yandex.webmaster3.storage.events.data.WMCEventContent;
import ru.yandex.webmaster3.storage.events.data.events.RetranslateToUsersEvent;
import ru.yandex.webmaster3.storage.events.data.events.UserDomainMessageEvent;
import ru.yandex.webmaster3.storage.events.data.events.UserHostMessageEvent;
import ru.yandex.webmaster3.storage.events.service.WMCEventsService;
import ru.yandex.webmaster3.storage.host.service.MirrorService2;
import ru.yandex.webmaster3.storage.user.message.content.MessageContent;
import ru.yandex.webmaster3.storage.user.notification.NotificationType;

@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class SiteProblemsNotificationService {
    private static final Map<SiteProblemTypeEnum, Duration> FLICKERING_CONTROL_PROBLEMS = new EnumMap<>(SiteProblemTypeEnum.class);
    private static final Map<SiteProblemSeverityEnum, NotificationType> SEVERITY_NT_TYPE_MAPPING = new EnumMap<>(SiteProblemSeverityEnum.class);
    private static final Map<SiteProblemTypeEnum, NotificationType> TURBO_PROBLEM_TYPE = new EnumMap<>(SiteProblemTypeEnum.class);

    static {
        FLICKERING_CONTROL_PROBLEMS.put(SiteProblemTypeEnum.THREATS, Duration.standardDays(1));
        FLICKERING_CONTROL_PROBLEMS.put(SiteProblemTypeEnum.SANCTIONS, Duration.standardDays(1));

        SEVERITY_NT_TYPE_MAPPING.put(SiteProblemSeverityEnum.FATAL, NotificationType.SITE_PROBLEM_FATAL);
        SEVERITY_NT_TYPE_MAPPING.put(SiteProblemSeverityEnum.CRITICAL, NotificationType.SITE_PROBLEM_CRITICAL);
        SEVERITY_NT_TYPE_MAPPING.put(SiteProblemSeverityEnum.POSSIBLE_PROBLEM, NotificationType.SITE_PROBLEM_POSSIBLE);
        SEVERITY_NT_TYPE_MAPPING.put(SiteProblemSeverityEnum.RECOMMENDATION, NotificationType.SITE_PROBLEM_RECOMMENDATION);

        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_DOCUMENT_BAN, NotificationType.TURBO_BAN);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_FEED_BAN, NotificationType.TURBO_BAN);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_HOST_BAN, NotificationType.TURBO_BAN);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_HOST_BAN_INFO, NotificationType.TURBO_BAN);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_ERROR, NotificationType.TURBO_ERROR);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_RSS_ERROR, NotificationType.TURBO_ERROR);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_YML_ERROR, NotificationType.TURBO_ERROR);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_WARNING, NotificationType.TURBO_WARNING);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_RSS_WARNING, NotificationType.TURBO_WARNING);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_YML_WARNING, NotificationType.TURBO_WARNING);
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_INSUFFICIENT_CLICKS_SHARE, NotificationType.TURBO_WARNING);
        //no notification for turbo listings
        TURBO_PROBLEM_TYPE.put(SiteProblemTypeEnum.TURBO_LISTING_ERROR, null);

    }


    private final RecentlySentChecklistNotificationsYDao recentlySentChecklistNotificationsYDao;
    private final MirrorService2 mirrorService2;
    private final WMCEventsService wmcEventsService;


    public void sendNotification(WebmasterHostId hostId, ProblemSignal signal, AbstractProblemInfo prevState, DateTime actualSince) {
        WMCEventContent event = createUserMessageEvent(hostId, signal, prevState, actualSince);
        if (event != null) {
            wmcEventsService.addEvent(event);
        }
    }

    public void sendNotification(Map<WebmasterHostId, Pair<RealTimeSiteProblemInfo, ProblemSignal>> map) {
        map.forEach((hostId, pair) -> {
            sendNotification(hostId, pair.getValue(), pair.getKey(), pair.getKey() == null ? DateTime.now() : pair.getKey().getActualSince());
        });
    }

    @Nullable
    public WMCEventContent createUserMessageEvent(WebmasterHostId hostId, ProblemSignal signal, AbstractProblemInfo prevState, DateTime actualSince) {
        if (signal.getState() != SiteProblemState.PRESENT) {
            return null;
        }
        if (signal.getProblemType().isDisabled()) {
            return null;
        }

        SiteProblemTypeEnum problemType = signal.getProblemType();
        if (problemType.isOnlyForMainMirror() && !mirrorService2.isMainMirror(hostId)) {
            return null;
        }
        if (!checkFlickeringControl(hostId, signal, prevState)) {
            return null;
        }

        NotificationType notificationType = SEVERITY_NT_TYPE_MAPPING.get(problemType.getSeverity());
        if (notificationType == null) {
            throw new IllegalArgumentException("Unknown problem severity " + problemType.getSeverity());
        }
        if (TURBO_PROBLEM_TYPE.containsKey(problemType)) {
            notificationType = TURBO_PROBLEM_TYPE.get(problemType);
        }
        MessageContent.ChecklistChanges content = new MessageContent.ChecklistChanges(hostId, problemType, signal.getLastUpdate(),
                actualSince, signal.getContent(), 0);
        if (problemType.isTurboProblem()) {
            return new RetranslateToUsersEvent<>(UserDomainMessageEvent.create(
                    hostId, content, notificationType, problemType.isCriticalSeverity()));
        } else {
            return new RetranslateToUsersEvent<>(UserHostMessageEvent.create(
                    hostId, content, notificationType, problemType.isCriticalSeverity()));
        }
    }

    private boolean checkFlickeringControl(WebmasterHostId hostId, ProblemSignal signal, AbstractProblemInfo prevState) {
        final SiteProblemTypeEnum problemType = signal.getProblemType();
        Duration flickeringDuration = FLICKERING_CONTROL_PROBLEMS.get(problemType);
        if (flickeringDuration != null) {
            try {
                RecentlySentProblemNotification lastNotification =
                        recentlySentChecklistNotificationsYDao.getLatestSentNotification(hostId, problemType, signal.getState());
                if (lastNotification != null && lastNotification.getDate().plus(flickeringDuration).isAfter(DateTime.now())) {
                    // Совсем костыль на случай, если в течение flickeringDuration уже посылалось письмо об этой проблеме
                    // Хорош только тем, что очень тупой, и смотрит в независимые таблицы, а не в таблицы из основного workflow системы
                    log.warn("Preventing flickering for host {} problem {} state {}", hostId, problemType, signal.getState());
                    return false;
                } else if (prevState != null && prevState.getActualSince() != null && prevState.getActualSince().plus(flickeringDuration).isAfterNow()) {
                    // Чуть мене злой костыль - не посылаем письмо, если предыдущее состояние поменялось в течение последних flickeringDuration
                    // В теории спасает от большего количества проблем, но завязан на то, что нет багов, аффектящих actualSince
                    log.warn("Preventing flickering for host {} problem {} state {}. Previous state is actual since {}", hostId, problemType, signal.getState(), prevState.getActualSince());
                    return false;
                }
                recentlySentChecklistNotificationsYDao.addNotificationRecord(hostId, problemType, signal.getState(), DateTime.now());
            } catch (Exception e) {
                log.error("Flickering control failed", e);
            }
        }
        return true;
    }
}