package ru.yandex.qe.dispenser.api.v1.request;

import java.io.IOException;
import java.util.Objects;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.util.JsonDeserializerBase;
import ru.yandex.qe.dispenser.api.util.JsonSerializerBase;
import ru.yandex.qe.dispenser.api.util.SerializationUtils;
import ru.yandex.qe.dispenser.api.v1.DiPerformer;
import ru.yandex.qe.dispenser.api.v1.DiQuotaChangeCause;

@JsonSerialize(using = DiOperation.Serializer.class)
@JsonDeserialize(using = DiOperation.Deserializer.class)
public final class DiOperation<T> {
    @NotNull
    private final DiActionType type;
    @NotNull
    private final T action;
    @Nullable
    private final DiPerformer performer;
    @Nullable // TODO: @NotNull after client sync
    private final DiQuotaChangeCause cause;

    public DiOperation(@NotNull final DiActionType type,
                       @NotNull final T action,
                       @Nullable final DiPerformer performer,
                       @Nullable final DiQuotaChangeCause cause) {
        this.type = type;
        this.action = action;
        this.performer = performer;
        this.cause = cause;
    }

    @NotNull
    public DiActionType getType() {
        return type;
    }

    @NotNull
    public T getAction() {
        return action;
    }

    @Nullable
    public DiPerformer getPerformer() {
        return performer;
    }

    @Nullable
    public DiQuotaChangeCause getCause() {
        return cause;
    }

    @Override
    public boolean equals(@Nullable final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final DiOperation<?> other = (DiOperation<?>) o;
        return type == other.type && action.equals(other.action) && Objects.equals(performer, other.performer);

    }

    @Override
    public int hashCode() {
        return 31 * (31 * type.hashCode() + action.hashCode()) + Objects.hashCode(performer);
    }

    static final class Serializer extends JsonSerializerBase<DiOperation<?>> {
        @Override
        public void serialize(@NotNull final DiOperation<?> operation,
                              @NotNull final JsonGenerator jg,
                              @NotNull final SerializerProvider sp) throws IOException {
            jg.writeStartObject();
            jg.writeObjectField("type", operation.getType());
            jg.writeObjectField("action", operation.getAction());
            if (operation.getPerformer() != null) {
                jg.writeObjectField("performer", operation.getPerformer());
            }
            jg.writeObjectField("cause", operation.cause);
            jg.writeEndObject();
        }
    }

    static final class Deserializer extends JsonDeserializerBase<DiOperation<?>> {
        @NotNull
        @Override
        @SuppressWarnings("LocalVariableHidesMemberVariable")
        public DiOperation<?> deserialize(@NotNull final JsonParser jp,
                                          @NotNull final DeserializationContext dc) throws IOException {
            final JsonNode json = toJson(jp);
            final DiActionType type = SerializationUtils.convertValue(json.get("type"), DiActionType.class);
            final DiPerformer performer = json.has("performer") ? SerializationUtils.convertValue(json.get("performer"), DiPerformer.class) : null;
            final DiQuotaChangeCause cause = json.has("cause") ? SerializationUtils.convertValue(json.get("cause"), DiQuotaChangeCause.class) : null;
            return type.convert(json.get("action"), performer, cause);
        }
    }
}
