package ru.yandex.webmaster3.worker.notifications;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import com.datastax.driver.core.utils.UUIDs;
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.host.verification.UserHostVerificationInfo;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.storage.events.data.WMCEventContent;
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.notifications.dao.HostRemovalNotificationsYDao;
import ru.yandex.webmaster3.storage.user.message.content.MessageContent;
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;

import static ru.yandex.webmaster3.core.host.verification.VerificationCausedBy.INITIAL_VERIFICATION;
import static ru.yandex.webmaster3.core.worker.task.PeriodicTaskType.UNVERIFIED_HOSTS_REMOVAL_WARNING;

@Component
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SendNotificationsAboutRemoveUnverifiedHostsTask extends PeriodicTask<PeriodicTaskState> {

    private final HostRemovalNotificationsYDao hostRemovalNotificationsYDao;
    private final UserHostsService userHostsService;
    private final WMCEventsService wmcEventsService;
    private final MirrorService2 mirrorService2;

    private static final int TOTAL_THREADS = 8;
    // не увеличивать
    private static final int BATCH_SIZE = 1000;
    private static final Duration HOST_AGE_FOR_WEEK_NOTIFICATIONS = Duration.standardDays(7);
    private static final Duration HOST_AGE_FOR_THREE_WEEK_NOTIFICATIONS = Duration.standardDays(21);


    @Override
    public Result run(UUID runId) throws Exception {
        sendNotification();
        return new Result(TaskResult.SUCCESS);
    }

    private void sendNotification() throws ExecutionException, InterruptedException {
        ExecutorService executorService = ru.yandex.common.util.concurrent.Executors.newBlockingFixedThreadPool(
                TOTAL_THREADS, TOTAL_THREADS,
                0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(TOTAL_THREADS),
                Executors.defaultThreadFactory());
        final DateTime now = DateTime.now();


        List<UserHostVerificationInfo> batchForFirstTypeNotification = new ArrayList<>(BATCH_SIZE);
        List<UserHostVerificationInfo> batchForSecondTypeNotification = new ArrayList<>(BATCH_SIZE);
        List<Future<Void>> futures = new ArrayList<>();

        try {
            userHostsService.forEachUserUnverifiedHosts(info -> {
                if (info.getRecordId() == null) {
                    log.error("Null record id for : {}", info);
                    return;
                }
                // только главные зеркала и только при изначальной инициализации
                if (!mirrorService2.isMainMirror(info.getHostId()) || info.getVerificationCausedBy() != INITIAL_VERIFICATION) {
                    return;
                }

                final DateTime addDate = new DateTime(UUIDs.unixTimestamp(info.getRecordId()));

                if (addDate.plus(HOST_AGE_FOR_WEEK_NOTIFICATIONS).isAfter(now)) {
                    return;
                }

                if (addDate.plus(HOST_AGE_FOR_THREE_WEEK_NOTIFICATIONS).isAfter(now)) {
                    batchForFirstTypeNotification.add(info);
                } else {
                    batchForSecondTypeNotification.add(info);
                }

                if (batchForFirstTypeNotification.size() >= BATCH_SIZE) {
                    batchProcess(executorService, batchForFirstTypeNotification, futures,
                            HostRemovalNotificationsYDao.Type.WEEK);
                }

                if (batchForSecondTypeNotification.size() >= BATCH_SIZE) {
                    batchProcess(executorService, batchForSecondTypeNotification, futures,
                            HostRemovalNotificationsYDao.Type.THREE_WEEKS);
                }
            });

            if (!batchForFirstTypeNotification.isEmpty()) {
                batchProcess(executorService, batchForFirstTypeNotification, futures,
                        HostRemovalNotificationsYDao.Type.WEEK);
            }

            if (!batchForSecondTypeNotification.isEmpty()) {
                batchProcess(executorService, batchForSecondTypeNotification, futures,
                        HostRemovalNotificationsYDao.Type.THREE_WEEKS);
            }
            for (var f : futures) {
                f.get();
            }

        } catch (Exception e) {
            log.error("Error collecting unverified hosts", e);
            throw e;
        } finally {
            executorService.shutdownNow();
        }
    }

    private void batchProcess(ExecutorService executorService,
                              List<UserHostVerificationInfo> batchForNotification,
                              List<Future<Void>> futures,
                              HostRemovalNotificationsYDao.Type typeNotification) {
        var batchCopy = new ArrayList<>(batchForNotification);
        futures.add(executorService.submit(() -> sendNotificationByHosts(batchCopy, typeNotification)));
        batchForNotification.clear();
    }

    private Void sendNotificationByHosts(List<UserHostVerificationInfo> hostInfos,
                                         HostRemovalNotificationsYDao.Type type) {
        final List<Pair<Long, WebmasterHostId>> listUserIdWithHostId =
                hostInfos.stream().map(x -> Pair.of(x.getUserId(), x.getHostId())).toList();
        final Set<Pair<Long, WebmasterHostId>> hostsWithAlreadySentMessages
                = hostRemovalNotificationsYDao.selectByType(listUserIdWithHostId, type);

        final List<WMCEventContent> events = new ArrayList<>();
        final List<HostRemovalNotificationsYDao.UserHostInfo> recordsForInsert = new ArrayList<>();


        for (var info : hostInfos) {
            final Pair<Long, WebmasterHostId> key = Pair.of(info.getUserId(), info.getHostId());
            if (hostsWithAlreadySentMessages.contains(key)) {
                continue;
            }

            final MessageContent messageContent;
            final NotificationType notificationType;
            if (type == HostRemovalNotificationsYDao.Type.WEEK) {
                messageContent = new MessageContent.UnverifiedHostRemovalAfterWeek(info.getHostId());
                notificationType = NotificationType.UNVERIFIED_HOST_REMOVAL_AFTER_WEEK;
            } else {
                messageContent = new MessageContent.UnverifiedHostRemovalAfterThreeWeek(info.getHostId());
                notificationType = NotificationType.UNVERIFIED_HOST_REMOVAL_AFTER_THREE_WEEK;
            }
            WMCEventContent event = new UserHostMessageEvent<>(
                    info.getHostId(),
                    info.getUserId(),
                    messageContent,
                    notificationType,
                    false
            );
            events.add(event);
            recordsForInsert.add(new HostRemovalNotificationsYDao.UserHostInfo(info.getUserId(), info.getHostId(),
                    type));

        }
        hostRemovalNotificationsYDao.insert(recordsForInsert);
        wmcEventsService.addEventContents(events);
        return null;
    }

    @Override
    public PeriodicTaskType getType() {
        return UNVERIFIED_HOSTS_REMOVAL_WARNING;
    }

    @Override
    public TaskSchedule getSchedule() {
        return TaskSchedule.startByCron("0 13 6 * * *");
    }
}
