package ru.yandex.webmaster3.viewer.http.addurl;

import com.datastax.driver.core.utils.UUIDs;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ru.yandex.webmaster3.core.addurl.*;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.RequestPostProperty;
import ru.yandex.webmaster3.core.http.WriteAction;
import ru.yandex.webmaster3.core.http.request.UserDataAware;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.util.DailyQuotaUtil;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.worker.client.WorkerClient;
import ru.yandex.webmaster3.core.worker.task.UrlRecrawlTaskData;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.abt.model.Experiment;
import ru.yandex.webmaster3.storage.addurl.AddUrlRequestsService;
import ru.yandex.webmaster3.storage.spam.DeepSpamHostFilter;
import ru.yandex.webmaster3.storage.spam.FastSpamHostFilter;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostRequest;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author tsyplyaev
 */
@WriteAction
@Category("addurl")
@Component("/addurl/add")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class AddRecrawlUrlRequestsAction extends AbstractUserVerifiedHostAction<AddRecrawlUrlRequestsAction.Request, AddUrlResponse> {
    private final AddUrlRequestsService addUrlRequestsService;
    private final WorkerClient workerClient;
    private final DeepSpamHostFilter deepSpamHostFilter;
    private final FastSpamHostFilter fastSpamHostFilter;
    private final AbtService abtService;

    @Override
    public AddUrlResponse process(Request request) {
        DateTime now = DateTime.now();
        WebmasterHostId hostId = request.getHostId();

        DailyQuotaUtil.QuotaUsage quota = addUrlRequestsService.getQuotaUsage(hostId, now);

        if (quota.getQuotaRemain() <= 0) {
            return new AddUrlResponse.QuotaExceededResponse(this.getClass(), quota.getTodayQuota(), quota.getQuotaUsed());
        }

        //выкинем запросы, которые уже попадали в обработку (а также пустые строки)
        List<String> requestUrls = request.getUrls()
                .stream()
                .filter(StringUtils::isNotBlank) // выкинем пустые
                .distinct()
                .collect(Collectors.toList());
        List<String> invalidUrls = new ArrayList<>();
        if (addUrlRequestsService.balancerRequestExists(hostId, request.getBalancerRequestId().toString())) {
            //TODO: Это самый странный способ убрать дубликаты, что мне приходилось видеть. Нужно пофиксить. Сейчас упрощено из еще более странного способа
            requestUrls = Collections.emptyList();
        }

        AddUrlResponse.AddUrlWarningType warning = null;
        if (requestUrls.size() > quota.getQuotaRemain()) {
            requestUrls = requestUrls.subList(0, quota.getQuotaRemain());
            warning = AddUrlResponse.AddUrlWarningType.ADD_URL__QUOTA_EXCEEDED;
            invalidUrls.addAll(requestUrls.subList(quota.getQuotaRemain(), requestUrls.size()));
        }

        // сохраним в лог валидные урлы, но не попадающие в квоту
        parseUrls(invalidUrls,
                hostId,
                url -> addUrlRequestsService.saveLogs(
                        new UrlRecrawlEventLog(UUIDs.timeBased(),
                                IdUtils.hostIdToReadableUrl(hostId) + url,
                                now,
                                RecrawlSendingStatus.QUOTA_EXCEEDED,
                                request.getUserIp(),
                                request.getUserId())),
                url -> {
                }
        );

        Pair<List<UrlForRecrawl>, List<String>> filteredUrls =
                convertUrls(now, hostId, requestUrls);
        if (warning == null && !filteredUrls.getRight().isEmpty()) {
            warning = AddUrlResponse.AddUrlWarningType.ADD_URL__INVALID_URLS;
            invalidUrls.addAll(filteredUrls.getRight());
        }

        List<UrlForRecrawl> result = new ArrayList<>();
        List<OwnerRequest> ownerRequests = new ArrayList<>();
        List<UrlRecrawlTaskData> recrawlTasksData = new ArrayList<>();
        List<UrlRecrawlEventLog> eventLogs = new ArrayList<>();
        boolean spam = fastSpamHostFilter.checkHost(hostId) || deepSpamHostFilter.checkHost(hostId);
        for (UrlForRecrawl url : filteredUrls.getLeft()) {
            String fullUrl = url.getFullUrl();
            RecrawlSendingStatus status;

            if (spam) {
                // сразу пометим, как в процессе, чтобы не портить мониторинг
                url = url.changeState(RecrawlState.IN_PROGRESS, now);
                status = RecrawlSendingStatus.SPAM;
            } else {
                status = RecrawlSendingStatus.SENT;
                recrawlTasksData.add(UrlRecrawlTaskData.fromUrlForRecrawl(url));
            }

            if (fullUrl != null) {
                UrlRecrawlEventLog eventLog = new UrlRecrawlEventLog(url.getUrlId(), fullUrl,
                        now, status, request.getUserIp(), request.getUserId());
                eventLogs.add(eventLog);
            }

            result.add(url);
            ownerRequests.add(addUrlRequestsService.createOwnerRequest(url));
        }

        addUrlRequestsService.addBatch(result, request.getBalancerRequestId().toString());
        addUrlRequestsService.increaseQuotaUsage(ownerRequests);

        // И тестинг и прод пишут в один и тот же LB топик, разница лишь в том,
        // какие хосты попадают под эксперимент.
        if (abtService.isInExperiment(hostId, Experiment.URL_RECRAWL_SAMOVAR) && !recrawlTasksData.isEmpty()) {
            workerClient.enqueueBatch(recrawlTasksData);
        }

        addUrlRequestsService.saveLogs(eventLogs);

        return new AddUrlResponse.NormalResponse(quota.getTodayQuota(),
                quota.getQuotaUsed() + result.size(),
                Math.max(quota.getQuotaRemain() - result.size(), 0),
                result, warning, invalidUrls);
    }

    private Pair<List<UrlForRecrawl>, List<String>> convertUrls(DateTime now, WebmasterHostId hostId, List<String> requestedUrls) {
        List<UrlForRecrawl> urls = new ArrayList<>(requestedUrls.size());
        List<String> invalidUrls = new ArrayList<>(requestedUrls.size());

        parseUrls(requestedUrls, hostId,
                url -> urls.add(new UrlForRecrawl(hostId, UUIDs.timeBased(), url, now, now, RecrawlState.NEW)),
                invalidUrls::add);

        return Pair.of(urls, invalidUrls);
    }

    private void parseUrls(List<String> urls, WebmasterHostId hostId,
                           Consumer<String> validUrlConsumer, Consumer<String> invalidUrlConsumer) {
        for (String url : urls) {
            String relativeUrl = addUrlRequestsService.toRelativeUrlWithVerification(hostId, url);
            if (relativeUrl != null) {
                validUrlConsumer.accept(relativeUrl);
            } else {
                invalidUrlConsumer.accept(url);
            }
        }
    }

    public static class Request extends AbstractUserVerifiedHostRequest implements UserDataAware {
        private String userIp;
        private String yandexUid;
        private String frontendIp;
        private List<String> urls = Collections.emptyList();

        @Override
        public void setUserIp(String ip) {
            this.userIp = ip;
        }

        public String getUserIp() {
            return userIp;
        }

        @Override
        public void setYandexUid(String yandexUid) {
            this.yandexUid = yandexUid;
        }

        public String getYandexUid() {
            return yandexUid;
        }

        @Override
        public void setFrontendIp(String frontendIp) {
            this.frontendIp = frontendIp;
        }

        public String getFrontendIp() {
            return frontendIp;
        }

        @RequestPostProperty
        public void setUrls(List<String> urls) {
            this.urls = urls;
        }

        public List<String> getUrls() {
            return urls;
        }
    }
}
