package ru.yandex.webmaster3.storage.checklist.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.Duration;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemTypeEnum;
import ru.yandex.webmaster3.core.data.WebmasterHostGeneration;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.core.util.W3Collectors;
import ru.yandex.webmaster3.core.util.concurrent.graph.GraphExecution;
import ru.yandex.webmaster3.core.util.concurrent.graph.GraphExecutionBuilder;
import ru.yandex.webmaster3.storage.checklist.data.AbstractProblemInfo;
import ru.yandex.webmaster3.storage.host.AllHostsCacheService;

/**
 * Created by Oleg Bazdyrev on 06/04/2021.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AllHostsProblemsService {

    private final AllHostsCacheService allHostsCacheService;
    private final ApplicationContext applicationContext;

    private List<SiteProblemsProvider> problemsProviders;

    public void init() {
        problemsProviders = new ArrayList<>();
        for (String beanName : applicationContext.getBeanNamesForType(SiteProblemsProvider.class)) {
            SiteProblemsProvider provider = (SiteProblemsProvider) applicationContext.getBean(beanName);
            problemsProviders.add(provider);
        }
    }

    public void forEachProblem(Consumer<AbstractProblemInfo> consumer) {
        GraphExecutionBuilder builder = GraphExecutionBuilder.newBuilder("checklist-problem-uploader");
        final GraphExecutionBuilder.Queue<AbstractProblemInfo> writer = builder.process(() ->
                (List<AbstractProblemInfo> batch) -> {
                    batch.forEach(consumer);
                }).concurrency(1).name("writer").getInput();

        final GraphExecutionBuilder.Queue<WebmasterHostGeneration> problemResolver = builder.process(writer, (outQueue) ->
                (List<WebmasterHostGeneration> batch) -> {
                    for (SiteProblemsProvider problemProvider : problemsProviders) {
                        try {
                            Map<WebmasterHostId, List<? extends AbstractProblemInfo>> hostsProblems = RetryUtils.query(
                                    RetryUtils.linearBackoff(3, Duration.standardSeconds(30L)),
                                    () -> problemProvider.listProblems(batch)
                            );
                            // remove duplicates
                            Map<Pair<WebmasterHostId, SiteProblemTypeEnum>, ? extends AbstractProblemInfo> problems = hostsProblems
                                    .values().stream().flatMap(Collection::stream)
                                    .collect(Collectors.toMap(problemInfo -> Pair.of(problemInfo.getHostId(), problemInfo.getProblemType()),
                                            Function.identity(), W3Collectors.replacingMerger()));

                            for (AbstractProblemInfo problem : problems.values()) {
                                outQueue.put(problem);
                            }
                        } catch (WebmasterException e) {
                            log.error("Error resolving problems for batch", e);
                        }
                    }
                })
                .concurrency(8)
                .batchLimit(1024)
                .name("resolver")
                .getInput();

        GraphExecution<WebmasterHostGeneration> graph = builder.build(problemResolver);
        graph.start();

        try {
            allHostsCacheService.foreachEntry((hostId, generationId) -> {
                try {
                    graph.put(new WebmasterHostGeneration(hostId, generationId));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e);
                }
            });
            graph.doneWritingAndAwaitTermination();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new WebmasterException("Error when uploading problems for digest",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), "Error"), e);
        } catch (ExecutionException e) {
            throw new WebmasterException("Error when uploading problems for digest",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), "Error"), e);
        } finally {
            graph.terminateAbruptly();
        }
    }
}
