package ru.yandex.webmaster3.worker.problem;

import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Range;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemContent;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemTypeEnum;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
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.checklist.data.ProblemSignal;
import ru.yandex.webmaster3.storage.checklist.service.SiteProblemsService;
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.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
 * 18.02.2021
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class ImportInsignificantCGIParametersTask extends PeriodicTask<ImportInsignificantCGIParametersTask.State> {
    private static final int BATCH_SIZE = 2000;
    private static final int MAX_CGI_COUNT = 5;
    private final YtService ytService;
    private final SiteProblemsService siteProblemsService;
    private final CommonDataStateYDao commonDataStateYDao;
    private final AbtService abtService;
    @Value("${webmaster3.worker.problems.cgi_parameters.path}")
    private YtPath path;


    @Override
    public Result run(UUID runId) throws Exception {
        var dateTime = DateTime.now();
        setState(new State());
        DateTime lastImportedTable = Optional.ofNullable(commonDataStateYDao.getValue(CommonDataType.LAST_INSIGNIFICANT_CGI_PARAMETERS_UPDATE))
                .map(state -> new DateTime(Long.parseLong(state.getValue()) * 1000))
                .orElse(DateTime.parse("2021-01-01T00:00:00Z"));

        ytService.withoutTransaction(cypressService -> {
            YtPath tablePath = findTable(lastImportedTable, cypressService);
            if (tablePath == null) {
                return false;
            }

            var updateDateTime = new DateTime(Long.parseLong(tablePath.getName()) * 1000);

            processTable(tablePath, cypressService, updateDateTime);
            commonDataStateYDao.update(new CommonDataState(CommonDataType.LAST_INSIGNIFICANT_CGI_PARAMETERS_UPDATE, tablePath.getName(), DateTime.now()));
            siteProblemsService.notifyCleanableProblemUpdateFinished(SiteProblemTypeEnum.INSIGNIFICANT_CGI_PARAMETER, dateTime);

            return true;

        });
        return Result.SUCCESS;
    }

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

        try (AsyncTableReader.TableIterator<YtRow> iterator = tableReader.read()) {
            Map<WebmasterHostId, ProblemSignal> map = new HashMap<>(BATCH_SIZE);
            String lastHostId = null;
            Map<String, List<String>> cgiMap = new HashMap<>(MAX_CGI_COUNT);
            while (iterator.hasNext()) {
                final YtRow next = iterator.next();
                final WebmasterHostId hostId = IdUtils.urlToHostId(next.host);
                if (lastHostId == null || !lastHostId.equals(next.host)) {
                    state.totalHostProcessed++;
                    cgiMap = new HashMap<>(MAX_CGI_COUNT);
                    map.put(hostId, ProblemSignal.createPresent(new SiteProblemContent.InsignificantCGIParameter(cgiMap), updateDateTime));
                    lastHostId = next.host;
                    cgiMap.put(next.cgi, next.samples);
                } else {
                    if (cgiMap.size() >= MAX_CGI_COUNT) {
                        continue;
                    }
                    cgiMap.put(next.cgi, next.samples);
                }
                if (map.size() > BATCH_SIZE) {
                    state.processed += map.size();
                    siteProblemsService.updateCleanableProblems(map, SiteProblemTypeEnum.INSIGNIFICANT_CGI_PARAMETER);
                    map.clear();
                }

            }

            if (map.size() > 0) {
                state.processed += map.size();
                siteProblemsService.updateCleanableProblems(map, SiteProblemTypeEnum.INSIGNIFICANT_CGI_PARAMETER);
                map.clear();
            }
            siteProblemsService.updateCleanableProblems(map, SiteProblemTypeEnum.INSIGNIFICANT_CGI_PARAMETER);

        } catch (IOException | InterruptedException e) {
            throw new WebmasterException("YT error",
                    new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), e), e);
        }
    }


    public YtPath findTable(DateTime lastImportTableDate, YtCypressService cypressService) {
        return cypressService.list(path)
                .stream()
                .map(YtPath::getName)
                .filter(tn -> new DateTime(Long.valueOf(tn) * 1000).isAfter(lastImportTableDate))
                .max(Comparator.naturalOrder())
                .map(name -> YtPath.path(path, name))
                .orElse(null);
    }

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

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

    public class State implements PeriodicTaskState {
        @Getter
        int totalHostProcessed = 0;
        @Getter
        int processed = 0;
        @Getter
        int filteredRecords = 0;
    }

    public static class YtRow {
        String host;
        String cgi;

        List<String> samples;

        @JsonCreator
        public YtRow(@JsonProperty("Host") String host,
                     @JsonProperty("CGI") String cgi,
                     @JsonProperty("samples") List<String> samples) {
            this.host = host;
            this.cgi = cgi;
            this.samples = samples;
        }
    }
}
