package ru.yandex.webmaster3.worker.iks;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Range;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.abt.model.Experiment;
import ru.yandex.webmaster3.storage.events.data.WMCEvent;
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.settings.dao.CommonDataStateYDao;
import ru.yandex.webmaster3.storage.user.message.content.MessageContent;
import ru.yandex.webmaster3.storage.user.message.iks.IksMessageContent;
import ru.yandex.webmaster3.storage.user.message.iks.IksMessageType;
import ru.yandex.webmaster3.storage.user.notification.NotificationType;
import ru.yandex.webmaster3.storage.util.yt.AsyncTableReader;
import ru.yandex.webmaster3.storage.util.yt.YtCypressService;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtTableReadDriver;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

/**
 * ishalaru
 * 03.11.2020
 **/
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class IksRivalsNotificationPeriodicTask extends PeriodicTask<IksRivalsNotificationPeriodicTask.State> {
    private static final int BATCH_SIZE = 5000;
    @Value("${external.yt.service.arnold.root.default}/iks/notification/")
    private YtPath path;
    private final YtService ytService;
    private final CommonDataStateYDao commonDataStateYDao;
    private final WMCEventsService wmcEventsService;
    private final AbtService abtService;


    @Override
    public Result run(UUID runId) throws Exception {
        setState(new State());
        final LocalDate lastImportedDate = Optional.ofNullable(commonDataStateYDao.getValue(CommonDataType.LAST_IKS_RIVALS_NOTIFICATION_SEND)).
                map(state -> LocalDate.parse(state.getValue()))
                .orElse(LocalDate.parse("2020-11-03"));


        ytService.inTransaction(path).execute(cypressService -> {
            List<YtPath> tableForProcess = findTableToProcess(lastImportedDate, cypressService);
            if (tableForProcess.isEmpty()) {
                log.info("Did not find table to process. Last processed table: {}. Finishing task",
                        lastImportedDate);
                return false;
            }
            tableForProcess.sort(Comparator.comparing(YtPath::toString));
            final YtPath tablePath = tableForProcess.get(tableForProcess.size() - 1);
            processTable(tablePath, cypressService);
            commonDataStateYDao.update(new CommonDataState(CommonDataType.LAST_IKS_RIVALS_NOTIFICATION_SEND, tablePath.getName().substring(tablePath.getName().length() - 10), DateTime.now()));
            return true;
        });
        return Result.SUCCESS;
    }

    private void processTable(YtPath tablePath, YtCypressService cypressService) {
        AsyncTableReader<YtRow> tableReader =
                new AsyncTableReader<>(cypressService, tablePath, Range.all(),
                        YtTableReadDriver
                                .createYSONDriver(YtRow.class))
                        .splitInParts(100000)
                        .withThreadName("iks-rival-reader");

        try (AsyncTableReader.TableIterator<YtRow> iterator = tableReader.read()) {
            List<YtRow> list = new ArrayList<>();
            while (iterator.hasNext()) {
                final YtRow next = iterator.next();
                if (abtService.isInExperiment(next.getUuid(), Experiment.SQL_RIVAL_NOTIFICATION)) {
                    list.add(next);
                }
                if (list.size() >= BATCH_SIZE) {
                    send(list);
                    list.clear();
                }
            }
            if (!list.isEmpty()) {
                send(list);
            }
        } catch (IOException | InterruptedException e) {
            throw new WebmasterException("YT error",
                    new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), e), e);
        }
    }


    public void send(List<YtRow> list) {
        final List<WMCEvent> collect = list.stream().map(this::create).collect(Collectors.toList());
        wmcEventsService.addEvents(collect);
    }

    public WMCEvent create(YtRow row) {
        String type = row.getHostsId().size() == 1 ? "TYPE_ONE" : "TYPE_MULTI";
        return WMCEvent.create(
                new UserHostMessageEvent<>(
                        IdUtils.stringToHostId(row.getHostsId().get(0)),
                        Long.valueOf(row.getUuid()),
                        new MessageContent.IksNewInfo(List.of(), List.of(), IdUtils.stringToHostId(row.getHostsId().get(0)),
                                new IksMessageContent.IksRival(row.getUuid(), row.getHostsId().stream().limit(10).collect(Collectors.toList())),
                                IksMessageType.RIVAL_UPDATE,
                                type),
                        NotificationType.IKS_UPDATE,
                        false)
        );

    }

    private List<YtPath> findTableToProcess(LocalDate tableImportedLastTime,
                                            YtCypressService cypressService) {
        return cypressService.list(path)
                .stream()
                .map(YtPath::getName)
                .filter(e -> e.indexOf("iks_rival_update_notification_") >= 0)
                .filter(n -> LocalDate.parse(n.substring("iks_rival_update_notification_".length()), ISODateTimeFormat.yearMonthDay()).isAfter(tableImportedLastTime))
                .sorted(Comparator.naturalOrder())
                .map(name -> YtPath.path(path, name))
                .collect(Collectors.toList());
    }

    @lombok.Value
    public static final class YtRow {
        @JsonProperty("user_id")
        Long uuid;
        @JsonProperty("hosts")
        List<String> hostsId;
    }

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

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

    public static class State implements PeriodicTaskState {
    }
}
