package ru.yandex.webmaster3.worker.feeds;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.mutable.MutableObject;
import org.joda.time.DateTime;
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.feeds.feed.FeedServiceType;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedInfo2;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedStatus;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.http.WriteAction;
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.feeds.FeedsNative2YDao;
import ru.yandex.webmaster3.storage.feeds.logs.FeedsOffersLogsHistoryCHDao;
import ru.yandex.webmaster3.storage.feeds.logs.FeedsOffersLogsHistoryCHDao.FeedRecord;
import ru.yandex.webmaster3.storage.feeds.models.FeedStats;
import ru.yandex.webmaster3.storage.feeds.logs.SerpdataLogsHistoryCHDao;
import ru.yandex.webmaster3.storage.util.yt.YtColumn;
import ru.yandex.webmaster3.storage.util.yt.YtException;
import ru.yandex.webmaster3.storage.util.yt.YtNode;
import ru.yandex.webmaster3.storage.util.yt.YtNodeAttributes;
import ru.yandex.webmaster3.storage.util.yt.YtOperationId;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtSchema;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtTableData;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

/**
 * Created by Oleg Bazdyrev on 02/03/2022.
 */
@Slf4j
@WriteAction
@Component("/feeds/export")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ExportFeedsInfoPeriodicTask extends PeriodicTask<PeriodicTaskState> {

    @Value("${external.yt.service.hahn.root.default}/analytics/feeds/feeds-statuses-info")
    private final YtPath tablePath;
    private final YtService ytService;
    private final FeedsNative2YDao feedsNative2YDao;
    private final FeedsOffersLogsHistoryCHDao feedsOffersLogsHistoryCHDao;
    private final SerpdataLogsHistoryCHDao serpdataLogsHistoryCHDao;

    private interface F {
        YtSchema SCHEMA = new YtSchema();
        YtColumn<String> DOMAIN = SCHEMA.addColumn("Domain", YtColumn.Type.STRING);
        YtColumn<String> URL = SCHEMA.addColumn("Url", YtColumn.Type.STRING);
        YtColumn<Long> USER_ID = SCHEMA.addColumn("UserId", YtColumn.Type.UINT_64);
        YtColumn<List<Integer>> REGION_IDS = SCHEMA.addColumn("RegionIds", YtColumn.Type.any());
        YtColumn<String> LOGIN = SCHEMA.addColumn("Login", YtColumn.Type.STRING);
        YtColumn<String> PASSWORD = SCHEMA.addColumn("Password", YtColumn.Type.STRING);
        YtColumn<String> STATUS = SCHEMA.addColumn("Status", YtColumn.Type.STRING);
        YtColumn<List<String>> ERRORS = SCHEMA.addColumn("Errorsd", YtColumn.Type.any());
        YtColumn<String> STATUS_SCC = SCHEMA.addColumn("StatusScc", YtColumn.Type.STRING);
        YtColumn<List<String>> ERRORS_SCC = SCHEMA.addColumn("ErrorsScc", YtColumn.Type.any());
        YtColumn<String> TYPE = SCHEMA.addColumn("Type", YtColumn.Type.STRING);
        YtColumn<Set<FeedServiceType>> ENABLED_SERVICE_TYPES = SCHEMA.addColumn("EnabledServiceTypes", YtColumn.Type.any());
        YtColumn<Long> ADD_DATE = SCHEMA.addColumn("AddDate", YtColumn.Type.UINT_64);
        YtColumn<Long> UPDATE_DATE = SCHEMA.addColumn("UpdateDate", YtColumn.Type.UINT_64);
        YtColumn<Boolean> ENABLED = SCHEMA.addColumn("Enabled", YtColumn.Type.BOOLEAN);
        YtColumn<Long> BUSINESS_ID = SCHEMA.addColumn("BusinessId", YtColumn.Type.UINT_64);
        YtColumn<Long> PARTNER_ID = SCHEMA.addColumn("PartnerId", YtColumn.Type.UINT_64);
        YtColumn<Long> FEED_ID = SCHEMA.addColumn("FeedId", YtColumn.Type.UINT_64);
        YtColumn<Long> SCC_TIMESTAMP = SCHEMA.addColumn("SccTimestamp", YtColumn.Type.UINT_64);
        YtColumn<List<Map<String, Object>>> ERRORS_OFFER_BASE = SCHEMA.addColumn("ErrorOfferBase", YtColumn.Type.any());
        YtColumn<Long> TOTAL_OFFERS = SCHEMA.addColumn("TotalOffers", YtColumn.Type.UINT_64);
        YtColumn<Long> VALID_OFFERS = SCHEMA.addColumn("ValidOffers", YtColumn.Type.UINT_64);
        YtColumn<Long> ERROR_OFFERS = SCHEMA.addColumn("ErrorOffers", YtColumn.Type.UINT_64);
        YtColumn<Long> WARNING_OFFERS = SCHEMA.addColumn("WarningOffers", YtColumn.Type.UINT_64);
    }

    @Override
    public Result run(UUID runId) throws Exception {
        MutableObject<String> lastDomain = new MutableObject<>();
        List<NativeFeedInfo2> feedInfos = new ArrayList<>();
        YtTableData tableData = ytService.prepareTableData("feeds-infos", tw -> {
            feedsNative2YDao.forEach(info -> {
                if (lastDomain.getValue() != null && !lastDomain.getValue().equals(info.getDomain())) {
                    String domain = lastDomain.getValue();
                    List<String> feedUrls = feedInfos.stream().map(NativeFeedInfo2::getUrl).collect(Collectors.toList());
                    // TODO GoodsOffersLogsHistoryCHDao
                    Map<String, FeedRecord> offersStateByUrl = feedsOffersLogsHistoryCHDao.getLastState(feedUrls)
                            .stream().filter(Objects::nonNull).collect(Collectors.toMap(FeedRecord::getUrl, Function.identity()));
                    Map<String, FeedRecord> serpdataStateByUrl = serpdataLogsHistoryCHDao.getLastState(domain, null, null)
                            .stream().filter(Objects::nonNull).collect(Collectors.toMap(FeedRecord::getUrl, Function.identity()));
                    for (NativeFeedInfo2 rawFeed : feedInfos) {
                        FeedRecord offerState = offersStateByUrl.get(rawFeed.getUrl());
                        FeedRecord serpdataState = serpdataStateByUrl.get(rawFeed.getUrl());
                        FeedStats offerStats = Optional.ofNullable(offerState).map(FeedRecord::getStats).orElse(FeedStats.EMPTY);
                        FeedStats errorStats = Optional.ofNullable(offerState).map(FeedRecord::getErrorStats).orElse(FeedStats.EMPTY);
                        FeedStats serpdataStats = Optional.ofNullable(serpdataState).map(FeedRecord::getErrorStats).orElse(FeedStats.EMPTY);
                        // change status
                        long errorCount = errorStats.getError() + serpdataStats.getError();
                        // status calc (non-stores)
                        NativeFeedStatus status;
                        if (offerStats == FeedStats.EMPTY) {
                            status = NativeFeedStatus.IN_PROGRESS;
                        } else if (offerStats.getOk() == 0) {
                            status = NativeFeedStatus.FAILED;
                        } else if (errorStats.getError() > 0 || errorStats.getWarning() > 0 || serpdataStats.getError() > 0 || serpdataStats.getWarning() > 0) {
                            status = NativeFeedStatus.WARNING;
                        } else {
                            status = NativeFeedStatus.SUCCESS;
                        }
                        DateTime lastAccess = offerState == null ? rawFeed.getUpdateDate() : offerState.getLastAccess();
                        rawFeed = rawFeed.toUrlWithUserInfo();
                        F.DOMAIN.set(tw, domain);
                        F.URL.set(tw, rawFeed.getUrl());
                        F.USER_ID.set(tw, rawFeed.getUserId());
                        F.REGION_IDS.set(tw, rawFeed.getRegionsId());
                        F.LOGIN.set(tw, rawFeed.getLogin());
                        F.PASSWORD.set(tw, rawFeed.getPassword());
                        F.STATUS.set(tw, status.name());
                        F.ERRORS.set(tw, rawFeed.getErrors());
                        F.STATUS_SCC.set(tw, rawFeed.getStatusScc().name());
                        F.ERRORS_SCC.set(tw, rawFeed.getErrorsScc());
                        F.TYPE.set(tw, rawFeed.getType().name());
                        F.ENABLED_SERVICE_TYPES.set(tw, rawFeed.getEnabledServiceTypes());
                        F.ADD_DATE.set(tw, rawFeed.getAddDate() == null ? null : rawFeed.getAddDate().getMillis());
                        F.UPDATE_DATE.set(tw, lastAccess == null ? null : lastAccess.getMillis());
                        F.ENABLED.set(tw, rawFeed.isEnabled());
                        F.BUSINESS_ID.set(tw, rawFeed.getBusinessId());
                        F.PARTNER_ID.set(tw, rawFeed.getPartnerId());
                        F.FEED_ID.set(tw, rawFeed.getFeedId());
                        F.SCC_TIMESTAMP.set(tw, rawFeed.getSccTimestamp() == null ? null : rawFeed.getSccTimestamp().getMillis());
                        F.ERRORS_OFFER_BASE.set(tw, rawFeed.getErrorsOfferBase());
                        F.ERROR_OFFERS.set(tw, errorCount);
                        F.TOTAL_OFFERS.set(tw, errorStats.getTotal());
                        F.VALID_OFFERS.set(tw, errorStats.getOk());
                        F.WARNING_OFFERS.set(tw, errorStats.getWarning());
                        tw.rowEnd();
                    }

                    feedInfos.clear();
                }

                feedInfos.add(info);
                lastDomain.setValue(info.getDomain());
            });
        });
        ytService.inTransaction(tablePath).execute(cypressService -> {
            try {
                // создаем табличку
                YtNodeAttributes attributes = new YtNodeAttributes().setSchema(F.SCHEMA);
                cypressService.remove(tablePath, false, true);
                cypressService.create(tablePath, YtNode.NodeType.TABLE, true, attributes, true);
                // пишем
                cypressService.writeTable(tablePath, tableData);
                final YtOperationId operationId = cypressService.sort(tablePath, tablePath, F.DOMAIN.getName(), F.URL.getName());
                if (!cypressService.waitFor(operationId)) {
                    throw new WebmasterException("Could not wait for sort operation completion",
                            new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), null));
                }
            } catch (YtException e) {
                log.warn("Cannot write turbo hosts to " + tableData, e);
                throw new RuntimeException(e);
            }
            return true;
        });
        return new Result(TaskResult.SUCCESS);
    }

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

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