package ru.yandex.direct.http.smart.converter;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

import javax.annotation.Nullable;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import org.asynchttpclient.Response;

import ru.yandex.direct.http.smart.reflection.ProtoReflectionUtils;
import ru.yandex.direct.http.smart.reflection.Utils;
import ru.yandex.direct.utils.JsonUtils;

import static ru.yandex.direct.utils.FunctionalUtils.mapList;

public final class ProtoAsJsonResponseConverter implements ResponseConverter<Object> {

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private final JsonFormat.Parser parser = JsonFormat.parser().ignoringUnknownFields();

    @Override
    public Object convert(Response response, Type responseType) {
        if (Iterable.class.isAssignableFrom(Utils.getRawType(responseType))) {
            return tryProtoCollection(response, (ParameterizedType) responseType);
        } else {
            return tryProto(response, responseType);
        }
    }

    @Nullable
    private Object tryProto(Response response, Type responseType) {
        var method = ProtoReflectionUtils.getBuilderMethod(responseType);
        if (method == null) {
            throw new IllegalArgumentException("cannot deserialize response");
        }
        return parseProto(response.getResponseBody(DEFAULT_CHARSET), method);
    }

    @Nullable
    private Object tryProtoCollection(Response response, ParameterizedType responseType) {
        var parameterType = Utils.getParameterUpperBound(0, responseType);
        var method = ProtoReflectionUtils.getBuilderMethod(parameterType);
        if (method == null) {
            throw new IllegalArgumentException("cannot deserialize response");
        }
        String responseBody = response.getResponseBody(DEFAULT_CHARSET);
        var responseList = JsonUtils.fromJson(responseBody, new TypeReference<List<JsonNode>>() {
        });
        return mapList(responseList, item -> parseProto(item.toString(), method));
    }

    private Message parseProto(String raw, Method method) {
        var builder = ProtoReflectionUtils.getBuilder(method);
        try {
            parser.merge(raw, builder);
        } catch (InvalidProtocolBufferException e) {
            throw new IllegalArgumentException("Cannot deserialize response while parsing response body", e);
        }
        return builder.build();
    }
}
