package ru.yandex.webmaster3.api.turbo.action;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.api.http.auth.ActionPermission;
import ru.yandex.webmaster3.api.http.auth.Permission;
import ru.yandex.webmaster3.api.http.rest.AbstractApiAction;
import ru.yandex.webmaster3.api.http.rest.response.ApiResponse;
import ru.yandex.webmaster3.api.turbo.TurboApiErrorType;
import ru.yandex.webmaster3.api.turbo.data.TurboError;
import ru.yandex.webmaster3.api.turbo.data.TurboErrorContext;
import ru.yandex.webmaster3.api.turbo.data.TurboPage;
import ru.yandex.webmaster3.api.turbo.data.TurboPagesStatistics;
import ru.yandex.webmaster3.api.turbo.data.TurboPushProcessStatus;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.turbo.model.TurboUrl;
import ru.yandex.webmaster3.core.turbo.model.error.TurboRawError;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboApiTaskWithResult;
import ru.yandex.webmaster3.core.util.OptionalUtil;
import ru.yandex.webmaster3.storage.turbo.dao.api.TurboApiHostTasksYDao;

/**
 * Created by ifilippov5 on 23.05.18.
 */
@Category("turbo")
@Description("Возвращает статус пуша")
@ActionPermission(Permission.TURBO)
public abstract class AbstractGetTurboPushTaskStatusAction<Res extends ApiResponse> extends AbstractApiAction<GetTurboPushTaskStatusRequest, Res> {
    private static final Logger log = LoggerFactory.getLogger(AbstractGetTurboPushTaskStatusAction.class);

    private static final String NCRND_PARAM = "&ncrnd=";

    @Autowired
    private TurboApiHostTasksYDao turboApiHostTasksYDao;
    @Value("${webmaster3.api.turbo.turboUrlSuffix}")
    private String turboUrlSuffix;
    @Value("${webmaster3.api.turbo.turboHelpUrlPrefix}")
    private String turboHelpUrlPrefix;

    @Override
    public Res process(GetTurboPushTaskStatusRequest request) {
        try {
            TurboApiTaskWithResult task = turboApiHostTasksYDao.getFullTask(request.getHostId(), request.getPushId());
            if (task == null) {
                return renderTaskNotFoundResponse(request.getPushId());
            }
            return renderNormalResponse(new GetTurboPushTaskStatusResponse.NormalResponse(
                    TurboPushMode.fromActive(task.isActive()),
                    TurboPushProcessStatus.fromTaskState(task.getState()),
                    convertTurboUrls(task.getUrls()),
                    convertTurboErrors(task.getErrors()),
                    createStats(task.getUrls(), task.getErrors()))
            );
        } catch (Exception e) {
            throw new WebmasterException("Failed get turbo task status", new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null), e);
        }
    }

    @NotNull
    private List<TurboError> convertTurboErrors(List<TurboRawError> rawErrors) {
        if (rawErrors == null) {
            return Collections.emptyList();
        }
        return rawErrors.stream()
                .map(error -> new TurboError(
                        TurboApiErrorType.fromRawError(error),
                        getHelpLink(error),
                        Optional.ofNullable(error.getLine()),
                        Optional.ofNullable(error.getColumn()),
                        Optional.ofNullable(error.getText()),
                        OptionalUtil.mapCombine(
                                getOptionalParam(error.getParams(), "context", JsonNode::isTextual, JsonNode::textValue),
                                getOptionalParam(error.getParams(), "position", JsonNode::isInt, JsonNode::asInt),
                                TurboErrorContext::new
                        ),
                        getOptionalParam(error.getParams(), "tag", JsonNode::isTextual, JsonNode::textValue)
                ))
                .collect(Collectors.toList());
    }

    @NotNull
    private List<TurboPage> convertTurboUrls(List<TurboUrl> turboUrls) {
        if (turboUrls == null) {
            return Collections.emptyList();
        }
        return turboUrls.stream()
                .filter(turboUrl -> !Strings.isNullOrEmpty(turboUrl.getTurboUrl()))
                .map(turboUrl -> turboUrl.withTurboUrlSuffix(turboUrlSuffix + NCRND_PARAM + ThreadLocalRandom.current().nextLong()))
                .map(turboUrl -> new TurboPage(turboUrl.getUrl(), turboUrl.getTurboUrl(), turboUrl.getTitle()))
                .collect(Collectors.toList());
    }

    private static <T> Optional<T> getOptionalParam(ObjectNode params, String field, Predicate<JsonNode> predicate, Function<JsonNode, T> mapper) {
        return Optional.ofNullable(params)
                .map(node -> node.get(field))
                .filter(predicate)
                .map(mapper);
    }

    protected abstract Res renderNormalResponse(GetTurboPushTaskStatusResponse.NormalResponse response);

    protected abstract Res renderTaskNotFoundResponse(UUID pushId);

    private TurboPagesStatistics createStats(List<TurboUrl> turboUrls, List<TurboRawError> rawErrors) {
        if (turboUrls == null || rawErrors == null) {
            return null;
        }
        int errorsCount = 0;
        int warningsCount = 0;
        for (TurboRawError rawError : rawErrors) {
            TurboApiErrorType type = TurboApiErrorType.fromRawError(rawError);
            if (type == null) {
                continue;
            }
            if (type.isFatal()) {
                errorsCount++;
            } else {
                warningsCount++;
            }
        }
        return new TurboPagesStatistics(turboUrls.size(), errorsCount, warningsCount);
    }

    private String getHelpLink(TurboRawError rawError) {
        TurboApiErrorType apiError = TurboApiErrorType.fromRawError(rawError);
        return (apiError != null ? (turboHelpUrlPrefix + "#turbo-about__" + apiError.getHelpAnchor()) : turboHelpUrlPrefix);
    }

}
