package ru.yandex.qe.dispenser.ws.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ru.yandex.qe.dispenser.api.util.SerializationUtils;
import ru.yandex.qe.dispenser.domain.aspect.AspectBase;
import ru.yandex.qe.dispenser.domain.hierarchy.Session;
import ru.yandex.qe.dispenser.domain.request.RequestManager;
import ru.yandex.qe.dispenser.domain.util.ValidationUtils;
import ru.yandex.qe.dispenser.ws.Idempotent;
import ru.yandex.qe.dispenser.ws.Predictable;

import static ru.yandex.qe.dispenser.domain.util.ValidationUtils.validateReqId;

@Aspect
public class IdempotentAcpect extends AspectBase {
    private static final Logger LOG = LoggerFactory.getLogger(IdempotentAcpect.class);
    @Autowired
    private RequestManager requestManager;

    @Value("${idempotent.disabled}")
    private boolean isDisabled;

    @Around("execution(public * *(..)) && @annotation(idempotent)")
    public Object around(final @NotNull ProceedingJoinPoint jp, final @NotNull Idempotent idempotent) throws Throwable {
        if (isDisabled) {
            LOG.debug("Idempotent is disables");
            return jp.proceed();
        }
        final String reqId = ValidationUtils.requireNonNull(Session.REQID.get(), "No cgi param 'reqId' in request!");
        validateReqId(reqId);

        final String predicted = predict(jp);
        if (predicted != null) {
            requestManager.createRequest(reqId, predicted);
        }

        final Object result = jp.proceed();
        final String actual = SerializationUtils.writeValueAsString(result);
        if (!actual.equals(predicted)) {
            if (predicted != null) {
                LOG.error("predicted value mismatch! predicted : {} but actual was {}", predicted, actual);
            }
            requestManager.createRequest(reqId, actual);
        }
        return result;


    }

    private String predict(@NotNull final ProceedingJoinPoint jp) {
        String predictedAnswer = null;
        if (jp instanceof MethodInvocationProceedingJoinPoint
                && jp.getTarget() instanceof Predictable) {
            predictedAnswer = SerializationUtils.writeValueAsString(((Predictable) jp.getTarget()).predict(jp.getArgs()[0]));
        }
        return predictedAnswer;
    }

}
