package ru.yandex.webmaster3.coordinator.http.host;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import com.datastax.driver.core.utils.UUIDs;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Triple;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.data.WebmasterUser;
import ru.yandex.webmaster3.core.host.verification.UserHostVerificationInfo;
import ru.yandex.webmaster3.core.host.verification.VerificationCausedBy;
import ru.yandex.webmaster3.core.http.Action;
import ru.yandex.webmaster3.core.http.ActionRequest;
import ru.yandex.webmaster3.core.http.ActionResponse;
import ru.yandex.webmaster3.core.http.RequestQueryProperty;
import ru.yandex.webmaster3.core.http.WriteAction;
import ru.yandex.webmaster3.core.user.UserVerifiedHost;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.RetryUtils;
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.dao.UserHostVerificationYDao;
import ru.yandex.webmaster3.storage.user.dao.UserNotificationHostSettingsYDao;
import ru.yandex.webmaster3.storage.user.message.content.MessageContent;
import ru.yandex.webmaster3.storage.user.notification.HostNotificationSetting;
import ru.yandex.webmaster3.storage.user.notification.NotificationType;
import ru.yandex.webmaster3.storage.user.service.UserHostsService;


/**
 * @author kravchenko99
 * @date 9/7/21
 */

@Slf4j
@Component("/tmp/auto/add")
@WriteAction
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AutoAddMirrorsAction extends Action<AutoAddMirrorsAction.Req, AutoAddMirrorsAction.Res> {

    private final MirrorService2 mirrorService2;
    private final UserHostsService userHostsService;
    private final UserHostVerificationYDao userHostVerificationYDao;
    private final UserNotificationHostSettingsYDao userNotificationHostSettingsYDao;
    private final WMCEventsService wmcEventsService;

    @Override
    public Res process(Req request) throws WebmasterException {
        Map<Long, Map<WebmasterHostId, UserVerifiedHost>> users = new HashMap<>();
        userHostsService.forEach(x -> users.computeIfAbsent(x.getKey(), a -> new HashMap<>()).put(x.getValue().getWebmasterHostId(), x.getValue()));
        // user/hostId/mainMirror
        List<Triple<Long, WebmasterHostId, WebmasterHostId>> res = new ArrayList<>();
        for (var p : users.entrySet()) {
            Long userId = p.getKey();
            for (var hostId: p.getValue().keySet()) {
                WebmasterHostId mainMirror = mirrorService2.getMainMirror(hostId);
                if (mainMirror == null || mainMirror.equals(hostId)) {
                    continue;
                }
                boolean userHasMainMirror = p.getValue().containsKey(mainMirror);
                if (!userHasMainMirror && p.getValue().get(hostId).getVerificationType().isCanBeInherited() &&
                        isSuitableHosts(hostId, mainMirror)) {
                    res.add(Triple.of(userId, hostId, mainMirror));
                    log.info("userId - {}, hostId - {}, mainMirror - {}", userId, hostId, mainMirror);
                }
            }

            if (res.size() >= request.limit) {
                break;
            }
        }

        if (!request.dry) {

            res.forEach(x -> {
                try {
                    verifyHost(new WebmasterUser(x.getLeft()), x.getRight(), users.get(x.getLeft()).get(x.getMiddle()));
                } catch (InterruptedException e) {
                    throw new RuntimeException();
                }
            });
        }
        return new Res(res);
    }

    static boolean isSuitableHosts(WebmasterHostId hostId, WebmasterHostId newHostId) {
        return IdUtils.cutWWW(hostId).getPunycodeHostname().equals(IdUtils.cutWWW(newHostId).getPunycodeHostname());
    }

    private void verifyHost(WebmasterUser user,
                            WebmasterHostId newMainMirrorId,
                            UserVerifiedHost verificationRecord) throws InterruptedException {

        UUID recordId = UUIDs.timeBased();
        DateTime now = DateTime.now();

        UserHostVerificationInfo newVerificationInfoInProgress =
                UserHostVerificationInfo.createVerificationStartRecord(
                        recordId,
                        user.getUserId(),
                        newMainMirrorId,
                        verificationRecord.getVerificationType(),
                        verificationRecord.getVerificationUin(),
                        VerificationCausedBy.INITIAL_VERIFICATION
                );

        UserHostVerificationInfo newVerificationInfoSuccess =
                newVerificationInfoInProgress.copyAsVerified(recordId);

        UserVerifiedHost newVerificationRecordMainMirror =
                verificationRecord.withWebmasterHostId(newMainMirrorId).withVerificationDate(now);
        try {
            // Начинаем проверку, далее как-будто проверили и потом заканчиваем проверку, чтобы если вдруг на
            // какой-нибудь стадии вылетит ошибка сильно не разорвало консистентность
            List<HostNotificationSetting> settings = userNotificationHostSettingsYDao.getSettings(user,
                            List.of(verificationRecord.getWebmasterHostId()))
                    .stream().map(x -> x.withHostId(newMainMirrorId))
                    .collect(Collectors.toList());
            userHostVerificationYDao.addVerificationRecord(newVerificationInfoInProgress);

            RetryUtils.execute(RetryUtils.linearBackoff(2, Duration.standardSeconds(20)), () -> {
                userNotificationHostSettingsYDao.insert(user, settings);
                userHostsService.addVerifiedHost(user, newVerificationRecordMainMirror);
                userHostVerificationYDao.addVerificationRecord(newVerificationInfoSuccess);
            });
            log.info("Success userId - {}, hostId - {}", user.getUserId(), newMainMirrorId);

            UserHostMessageEvent<MessageContent.AutoAddMainMirror> event =
                    new UserHostMessageEvent<>(
                            verificationRecord.getWebmasterHostId(),
                            user.getUserId(),
                            new MessageContent.AutoAddMainMirror(
                                    verificationRecord.getWebmasterHostId(),
                                    verificationRecord.getWebmasterHostId(),
                                    newMainMirrorId),
                            NotificationType.MAIN_MIRROR_AUTO_ADD,
                            false
                    );

            RetryUtils.execute(RetryUtils.linearBackoff(2, Duration.standardSeconds(20)),
                    () -> wmcEventsService.addEvent(event)
            );
        } catch (InterruptedException e) {
            throw e;
        } catch (Exception e) {
            log.error("Error while verifying host - {} to user_id - {}", newMainMirrorId, user.getUserId(), e);
        }
    }

    @Setter(onMethod_ = @RequestQueryProperty)
    public static class Req implements ActionRequest {
        long limit = 1;
        @Setter(onMethod_ = @RequestQueryProperty(required = true))
        boolean dry;

    }

    @Value
    public static class Res implements ActionResponse.NormalResponse {
        List<Triple<Long, WebmasterHostId, WebmasterHostId>> res;
    }
}
