package ru.yandex.webmaster3.storage.delurl;

import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.addurl.UrlForRecrawl;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.data.WebmasterUser;
import ru.yandex.webmaster3.core.delurl.DelUrlRequest;
import ru.yandex.webmaster3.core.delurl.DelurlType;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.DailyQuotaUtil;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.core.util.UrlUtils;
import ru.yandex.webmaster3.storage.delurl.dao.DelUrlRequestsYDao;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.settings.SettingsService;
import ru.yandex.webmaster3.storage.user.UserTakeoutDataProvider;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;

import java.util.*;
import java.util.stream.Collectors;

import static ru.yandex.webmaster3.storage.host.CommonDataType.DELURL_PREFIX_QUOTA_PER_DAY;
import static ru.yandex.webmaster3.storage.host.CommonDataType.DELURL_URL_QUOTA_PER_DAY;

/**
 * @author leonidrom
 */
@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
@Slf4j
public class DelUrlRequestsService implements UserTakeoutDataProvider {
    private static final RetryUtils.RetryPolicy BACKOFF_RETRY_POLICY = RetryUtils.linearBackoff(5, Duration.standardSeconds(30));
    private static final RetryUtils.RetryPolicy INSTANT_POLICY = RetryUtils.instantRetry(3);
    private static final DateTimeZone DATE_TIME_ZONE = DateTimeZone.getDefault();
    private static final int MAX_DAYS_LOOKUP = 3;

    private final UrlUtils.CanonizeUrlForRobotWrapper canonizer = new UrlUtils.CanonizeUrlForRobotWrapper();
    private final DelUrlRequestsYDao delurlRequestsYDao;
    private final SettingsService settingsService;

    public void add(DelUrlRequest delurlRequest) {
        addBatch(Collections.singletonList(delurlRequest));
    }

    public void addBatch(List<DelUrlRequest> delurlRequests) {
        try {
            RetryUtils.execute(INSTANT_POLICY, () -> {
                delurlRequestsYDao.addBatch(delurlRequests);
            });
        } catch (Exception e) {
            throw new WebmasterException("Unable to insert request",
                    new WebmasterErrorResponse.YDBErrorResponse(this.getClass(), e), e);
        }
    }

    public void updateBatch(List<DelUrlRequest> batch) {
        // на всякий
        var batchPartition = Lists.partition(batch, 1024);
        try {
            for (var b : batchPartition) {
                RetryUtils.execute(BACKOFF_RETRY_POLICY, () -> {
                    delurlRequestsYDao.updateBatch(b);
                });
            }
        } catch (Exception ex) {
            throw new WebmasterException("Unable to save delurl request",
                    new WebmasterErrorResponse.YDBErrorResponse(this.getClass(), ex), ex);
        }
    }

    public DelUrlRequest get(WebmasterHostId hostId, UUID urlId) {
        return delurlRequestsYDao.get(hostId, urlId);
    }

    public int count(WebmasterHostId hostId, DateTime fromDate, DateTime toDate) {
        return delurlRequestsYDao.count(hostId, fromDate, toDate);
    }

    public List<DelUrlRequest> list(WebmasterHostId hostId, DateTime fromDate, DateTime toDate, int skip, int limit) {
        return delurlRequestsYDao.list(hostId, fromDate, toDate, skip, limit);
    }


    public List<DelUrlRequest> list(DateTime fromDate, DateTime toDate) {
        // В YDB пока нет нормального стриминга, поэтому тупо бьем интервал дат на промежутки
        long intervalLength = Duration.standardMinutes(30).getMillis();
        long endTS = toDate.getMillis();

        Set<DelUrlRequest> res = new HashSet<>();
        for (long startTS = fromDate.getMillis(); startTS < endTS; startTS += intervalLength + 1) {
            var reqs = delurlRequestsYDao.list(new DateTime(startTS), new DateTime(Math.min(startTS + intervalLength, endTS)));
            res.addAll(reqs);
        }

        return new ArrayList<>(res);
    }

    public DailyQuotaUtil.QuotaUsage getQuotaUsage(WebmasterHostId hostId, DelurlType type) {
        DateTime now = DateTime.now().withTimeAtStartOfDay();
        DateTime fromDate = now.minusDays(MAX_DAYS_LOOKUP);

        try {
            CommonDataType dataType = type == DelurlType.URL ? DELURL_URL_QUOTA_PER_DAY : DELURL_PREFIX_QUOTA_PER_DAY;
            int quota = settingsService.getSettingCached(dataType, Integer::parseInt);
            Map<LocalDate, Integer> usageByDate = delurlRequestsYDao.list(hostId, fromDate).stream()
                    .filter(delurlRequest -> delurlRequest.getType() == type)
                    .collect(
                            Collectors.groupingBy(
                                    r -> new LocalDate(r.getAddDate(), DATE_TIME_ZONE), // нет надежды на дефолтную таймзону
                                    Collectors.summingInt(r -> 1)
                            )
                    );
            TreeMap<LocalDate, Integer> usage = new TreeMap<>(usageByDate);
            return DailyQuotaUtil.computeRemainingQuotaDelUrl(LocalDate.now(DATE_TIME_ZONE), usage, quota, MAX_DAYS_LOOKUP);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Unable to get host quota",
                    new WebmasterErrorResponse.YDBErrorResponse(this.getClass(), e), e);
        }
    }

    public List<DelUrlRequest> getPendingRequests(WebmasterHostId hostId, String samovarUrl) {
        try {
            return RetryUtils.query(BACKOFF_RETRY_POLICY, () -> {
                return delurlRequestsYDao.list(hostId, DateTime.now().minus(UrlForRecrawl.STALE_REQUEST_AGE)).stream()
                        .filter(req -> !req.getState().isFinal())
                        .filter(req -> {
                            String canonicalUrl = canonizer.canonizeUrlForRobot(req.getFullUrl());
                            if (canonicalUrl == null) {
                                canonicalUrl = req.getFullUrl();
                            }

                            return canonicalUrl.equals(samovarUrl);
                        })
                        .collect(Collectors.toList());
            });

        } catch (Exception e) {
            throw new WebmasterException("Error reading from Ydb",
                    new WebmasterErrorResponse.YDBErrorResponse(this.getClass(), e), e);
        }
    }

    @Override
    public void deleteUserData(WebmasterUser user) {
        UserTakeoutDataProvider.super.deleteUserData(user);
    }

    @Override
    public @NotNull List<String> getTakeoutTables() {
        return List.of(delurlRequestsYDao.getTablePath());
    }
}
