package ru.yandex.webmaster3.worker.host.verification;

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

import com.datastax.driver.core.utils.UUIDs;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
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.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.user.UserVerifiedHost;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.storage.events.data.events.UserHostMessageEvent;
import ru.yandex.webmaster3.storage.events.service.WMCEventsService;
import ru.yandex.webmaster3.storage.host.CommonDataState;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.mirrors.dao.MirrorsChangesCHDao;
import ru.yandex.webmaster3.storage.settings.SettingsService;
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;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;


/**
 * @author kravchenko99
 * @date 5/25/21
 */


@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class AutoVerificationOnMigrationTask extends PeriodicTask<AutoVerificationOnMigrationTask.TaskState> {

    private final SettingsService settingsService;
    private final MirrorsChangesCHDao mdbMirrorsChangesCHDao;
    private final UserHostsService userHostsService;
    private final UserHostVerificationYDao userHostVerificationYDao;
    private final UserNotificationHostSettingsYDao userNotificationHostSettingsYDao;
    private final WMCEventsService wmcEventsService;

    private String checkLastImportedTable() {
        CommonDataState mirrorsState =
                settingsService.getSettingUncached(CommonDataType.LAST_IMPORTED_MIRRORS_CHANGES);
        CommonDataState lastProcessed =
                settingsService.getSettingUncached(CommonDataType.LAST_PROCESSED_MIRRORS_AUTO_VERIFICATION);
        if (mirrorsState == null || lastProcessed != null && Objects.equals(mirrorsState.getValue(),
                lastProcessed.getValue())) {
            // изменений не найдено
            return null;
        }
        return mirrorsState.getValue();
    }

    @Override
    public Result run(UUID runId) throws Exception {
        String version = checkLastImportedTable();
        if (version == null) {
            return Result.SUCCESS;
        }
        Map<Long, List<Pair<WebmasterHostId, WebmasterHostId>>> hostsByUserId = new HashMap<>();

        // host_id -- который подтвержден у пользователя, и new_main_mirror
        // list[(host_id, new_main_mirror_id)]
        Map<WebmasterHostId, WebmasterHostId> hostsChanged =
                mdbMirrorsChangesCHDao.getHostsForAutoVerification(version);

        // костыль, который обогащает пользователями.
        userHostsService.forEach(r -> {
            WebmasterHostId hostId = r.getValue().getWebmasterHostId();
            if (hostsChanged.containsKey(hostId)) {
                hostsByUserId.computeIfAbsent(r.getKey(), x -> new ArrayList<>()).add(Pair.of(hostId, hostsChanged.get(hostId)));
            }
        });
        TaskState taskState = new TaskState();
        setState(taskState);

        for (var entry : hostsByUserId.entrySet()) {
            long userId = entry.getKey();

            WebmasterUser user = new WebmasterUser(userId);
            taskState.usersCount++;
            List<Pair<WebmasterHostId, WebmasterHostId>> hosts = entry.getValue();
            taskState.allRowsCount += hosts.size();
            for (var p : hosts) {
                WebmasterHostId hostId = p.getKey();
                WebmasterHostId newMainMirrorId = p.getValue();
                List<UserVerifiedHost> verifiedHosts = userHostsService.getVerifiedHosts(user, List.of(hostId,
                        newMainMirrorId));

                Optional<UserVerifiedHost> verificationRecordHostIdOpt =
                        verifiedHosts.stream().filter(x -> x.getWebmasterHostId().equals(hostId)).findFirst();
                Optional<UserVerifiedHost> verificationRecordMainMirrorOpt =
                        verifiedHosts.stream().filter(x -> x.getWebmasterHostId().equals(newMainMirrorId)).findFirst();


                if (!isSuitableHosts(taskState, hostId, newMainMirrorId, verificationRecordHostIdOpt,
                        verificationRecordMainMirrorOpt)) {
                    continue;
                }

                UserVerifiedHost verificationRecord = verificationRecordHostIdOpt.get();
                verifyHost(user, newMainMirrorId, verificationRecord);
            }
        }

        settingsService.update(CommonDataType.LAST_PROCESSED_MIRRORS_AUTO_VERIFICATION, version);
        return Result.SUCCESS;
    }

    private void verifyHost(WebmasterUser user,
                            WebmasterHostId newMainMirrorId,
                            UserVerifiedHost verificationRecord) throws InterruptedException {
        UUID recordId = UUIDs.timeBased();
        DateTime now = DateTime.now();
        TaskState taskState = getState();

        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.debug("Success - {}", newMainMirrorId);
            taskState.successCount++;

            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);
            taskState.failedVerificationCount++;
        }
    }

    static boolean isSuitableHosts(TaskState taskState, WebmasterHostId hostId, WebmasterHostId newHostId,
                                   Optional<UserVerifiedHost> hostIdVerificationRecordOpt,
                                   Optional<UserVerifiedHost> newHostIdVerificationRecordOpt) {
        if (!IdUtils.cutWWW(hostId).getPunycodeHostname().equals(IdUtils.cutWWW(newHostId).getPunycodeHostname())) {
            // подтверждаем только в рамках смены http/https или www/не www
            log.debug("DiffDomains hostId - {}, newHostId - {}", hostId, newHostId);
            taskState.differentDomainsCount++;
            return false;
        }
        if (newHostIdVerificationRecordOpt.isPresent()) {
            // у пользователя есть хост который хотим добавить
            log.debug("AlreadyAdded hostId - {}, newHostId - {}", hostId, newHostId);
            taskState.mainMirrorAlreadyAddedCount++;
            return false;
        }
        if (hostIdVerificationRecordOpt.isEmpty()) {
            // такого не должно быть!!! -- хост не подтвержден, который был подтвержден когда собирали данные
            log.debug("Unexpected hostId - {}, newHostId - {}", hostId, newHostId);
            taskState.unexpectedSituationCount++;
            return false;
        }
        if (!hostIdVerificationRecordOpt.get().getVerificationType().isCanBeInherited()) {
            // или для данного способа мы не можем делать автоподтверждения
            log.debug("Uninherited hostId - {}, newHostId - {}", hostId, newHostId);
            taskState.verificationCantBeInherited++;
            return false;
        }
        return true;
    }

    @Getter
    public static class TaskState implements PeriodicTaskState {
        int mainMirrorAlreadyAddedCount = 0;
        int differentDomainsCount = 0;
        int unexpectedSituationCount = 0;
        int failedVerificationCount = 0;
        int verificationCantBeInherited = 0;
        int successCount = 0;
        int allRowsCount = 0;
        int usersCount = 0;
    }

    @Override
    public PeriodicTaskType getType() {
        return PeriodicTaskType.AUTO_VERIFICATION_ON_MIGRATION;
    }

    @Override
    public TaskSchedule getSchedule() {
        //0 */5 * * * *
        return TaskSchedule.startByCron("0 */10 * * * *");
    }
}
