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

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

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.DtoBuilder;
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.util.ValidationUtils;
import ru.yandex.qe.dispenser.api.v1.DiAmount;

@JsonSerialize(using = DiQuotaState.Serializer.class)
@JsonDeserialize(using = DiQuotaState.Deserializer.class)
public final class DiQuotaState {
    @NotNull
    private final String resourceKey;
    @NotNull
    private final String projectKey;
    @Nullable
    private final String quotaSpecKey;
    @Nullable
    private final DiAmount actual;
    @Nullable
    private final DiAmount max;
    @Nullable
    private final DiAmount ownMax;
    @NotNull
    private final Set<String> segmentKeys;

    @NotNull
    public static Builder forResource(@NotNull final String resourceKey) {
        return new Builder(resourceKey);
    }

    private DiQuotaState(@NotNull final Builder builder) {
        resourceKey = builder.resourceKey;
        projectKey = ValidationUtils.requireNonNull(builder.projectKey, "Project key is required");
        quotaSpecKey = builder.quotaSpecKey;
        actual = builder.actual;
        max = builder.max;
        ownMax = builder.ownMax;
        segmentKeys = Collections.unmodifiableSet(builder.segmentKeys);
        if (actual == null && max == null && ownMax == null) {
            throw new IllegalStateException("Specify actual or max field!");
        }
    }

    @NotNull
    public String getResourceKey() {
        return resourceKey;
    }

    @NotNull
    public String getProjectKey() {
        return projectKey;
    }

    @Nullable
    public String getQuotaSpecKey() {
        return quotaSpecKey;
    }

    @Nullable
    public DiAmount getActual() {
        return actual;
    }

    @Nullable
    public DiAmount getMax() {
        return max;
    }

    @Nullable
    public DiAmount getOwnMax() {
        return ownMax;
    }

    @NotNull
    public Set<String> getSegmentKeys() {
        return segmentKeys;
    }

    public static class Builder implements DtoBuilder<DiQuotaState> {
        @NotNull
        private final String resourceKey;
        @Nullable
        private String projectKey;
        @Nullable
        private String quotaSpecKey;
        @Nullable
        private DiAmount actual;
        @Nullable
        private DiAmount max;
        @Nullable
        private DiAmount ownMax;
        @NotNull
        private final Set<String> segmentKeys = new HashSet<>();

        private Builder(@NotNull final String resourceKey) {
            this.resourceKey = resourceKey;
        }

        @NotNull
        public Builder forProject(@NotNull final String projectKey) {
            this.projectKey = projectKey;
            return this;
        }

        @NotNull
        public Builder forKey(@NotNull final String quotaSpecKey) {
            this.quotaSpecKey = quotaSpecKey;
            return this;
        }

        @NotNull
        public Builder withActual(@NotNull final DiAmount actual) {
            this.actual = actual;
            return this;
        }

        @NotNull
        public Builder withMax(@NotNull final DiAmount max) {
            this.max = max;
            return this;
        }

        @NotNull
        public Builder withOwnMax(@NotNull final DiAmount ownMax) {
            this.ownMax = ownMax;
            return this;
        }

        @NotNull
        public Builder withSegments(@NotNull final Collection<String> segmentKeys) {
            this.segmentKeys.addAll(segmentKeys);
            return this;
        }

        @NotNull
        public Builder withSegments(@NotNull final String... segmentKeys) {
            return withSegments(Arrays.asList(segmentKeys));
        }

        @NotNull
        @Override
        public DiQuotaState build() {
            return new DiQuotaState(this);
        }

    }

    static final class Serializer extends JsonSerializerBase<DiQuotaState> {
        @Override
        public void serialize(@NotNull final DiQuotaState value,
                              @NotNull final JsonGenerator jg,
                              @NotNull final SerializerProvider sp) throws IOException {
            jg.writeStartObject();
            jg.writeStringField("resourceKey", value.getResourceKey());
            if (value.getQuotaSpecKey() != null) {
                jg.writeStringField("quotaSpecKey", value.getQuotaSpecKey());
            }
            jg.writeStringField("projectKey", value.getProjectKey());
            if (value.getActual() != null) {
                serialize("actual", value.getActual(), jg);
            }
            if (value.getMax() != null) {
                serialize("max", value.getMax(), jg);
            }

            if (value.getOwnMax() != null) {
                serialize("ownMax", value.getOwnMax(), jg);
            }

            jg.writeObjectField("segments", value.getSegmentKeys());

            jg.writeEndObject();
        }

        private void serialize(@NotNull final String key,
                               @NotNull final DiAmount amount,
                               @NotNull final JsonGenerator jg) throws IOException {
            jg.writeObjectFieldStart(key);
            jg.writeNumberField("value", amount.getValue());
            jg.writeObjectField("unit", amount.getUnit());
            jg.writeEndObject();
        }
    }

    static final class Deserializer extends JsonDeserializerBase<DiQuotaState> {
        @NotNull
        @Override
        public DiQuotaState deserialize(@NotNull final JsonParser jp,
                                        @NotNull final DeserializationContext dc) throws IOException {
            final JsonNode node = jp.getCodec().readTree(jp);
            final Builder builder = forResource(node.get("resourceKey").asText()).forProject(node.get("projectKey").asText());
            if (node.hasNonNull("actual")) {
                builder.withActual(SerializationUtils.convertValue(node.get("actual"), DiAmount.class));
            }
            if (node.hasNonNull("max")) {
                builder.withMax(SerializationUtils.convertValue(node.get("max"), DiAmount.class));
            }
            if (node.hasNonNull("ownMax")) {
                builder.withOwnMax(SerializationUtils.convertValue(node.get("ownMax"), DiAmount.class));
            }
            if (node.hasNonNull("quotaSpecKey")) {
                builder.forKey(node.get("quotaSpecKey").asText());
            }
            if (node.hasNonNull("segments")) {
                builder.withSegments(SerializationUtils.toStringSet(node.get("segments")));
            }
            return builder.build();
        }
    }
}
