package ru.yandex.qe.dispenser.ws.api.model.distribution;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.v1.DiAmount;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public final class DistributedQuota {

    @NotNull
    private final List<Update> updates;

    @JsonCreator
    public DistributedQuota(@JsonProperty("updates") @NotNull final List<Update> updates) {
        this.updates = updates;
    }

    public static Builder builder() {
        return new Builder();
    }

    @NotNull
    public List<Update> getUpdates() {
        return updates;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final DistributedQuota that = (DistributedQuota) o;
        return updates.equals(that.updates);
    }

    @Override
    public int hashCode() {
        return Objects.hash(updates);
    }

    @Override
    public String toString() {
        return "DistributedQuota{" +
                "updates=" + updates +
                '}';
    }

    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    public static final class Update {

        private final long requestId;
        private final List<Change> changes;
        private final String comment;

        @JsonCreator
        public Update(@JsonProperty("requestId") final long requestId,
                      @JsonProperty("changes") @NotNull final List<Change> changes,
                      @JsonProperty("comment") @Nullable final String comment) {
            this.requestId = requestId;
            this.changes = changes;
            this.comment = comment;
        }

        public static Builder builder() {
            return new Builder();
        }

        public long getRequestId() {
            return requestId;
        }

        public List<Change> getChanges() {
            return changes;
        }

        public String getComment() {
            return comment;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            final Update update = (Update) o;
            return requestId == update.requestId &&
                    Objects.equals(changes, update.changes) &&
                    Objects.equals(comment, update.comment);
        }

        @Override
        public int hashCode() {
            return Objects.hash(requestId, changes, comment);
        }

        @Override
        public String toString() {
            return "Update{" +
                    "requestId=" + requestId +
                    ", changes=" + changes +
                    ", comment='" + comment + '\'' +
                    '}';
        }

        @JsonInclude(JsonInclude.Include.NON_EMPTY)
        public static final class Change {

            @NotNull
            private final String resourceKey;
            @NotNull
            private final Set<String> segmentKeys;
            @NotNull
            private final DiAmount amountReady;
            @Nullable
            private final DiAmount amountAllocated;

            @JsonCreator
            public Change(@JsonProperty("resourceKey") @NotNull final String resourceKey,
                          @JsonProperty("segmentKeys") @NotNull final Set<String> segmentKeys,
                          @JsonProperty("amountReady") @NotNull final DiAmount amountReady,
                          @JsonProperty("amountAllocated") @Nullable final DiAmount amountAllocated) {
                this.resourceKey = resourceKey;
                this.segmentKeys = segmentKeys;
                this.amountReady = amountReady;
                this.amountAllocated = amountAllocated;
            }

            public static Builder builder() {
                return new Builder();
            }

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

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

            @NotNull
            @JsonSerialize(using = DiAmount.CompactSerializer.class)
            public DiAmount getAmountReady() {
                return amountReady;
            }

            @Nullable
            @JsonSerialize(using = DiAmount.CompactSerializer.class)
            public DiAmount getAmountAllocated() {
                return amountAllocated;
            }

            public static final class Builder {

                @Nullable
                private String resourceKey;
                @NotNull
                private Set<String> segmentKeys = new HashSet<>();
                @Nullable
                private DiAmount amountReady;
                @Nullable
                private DiAmount amountAllocated;

                private Builder() {
                }

                @NotNull
                public Builder resourceKey(@NotNull final String resourceKey) {
                    Objects.requireNonNull(resourceKey, "Resource key is required.");
                    this.resourceKey = resourceKey;
                    return this;
                }

                @NotNull
                public Builder segmentKeys(@NotNull final Set<String> segmentKeys) {
                    Objects.requireNonNull(segmentKeys, "Segment keys are required.");
                    this.segmentKeys = segmentKeys;
                    return this;
                }

                @NotNull
                public Builder amountReady(@NotNull final DiAmount amountReady) {
                    Objects.requireNonNull(amountReady, "Amount ready is required.");
                    this.amountReady = amountReady;
                    return this;
                }

                @NotNull
                public Builder amountAllocated(@NotNull final DiAmount amountAllocated) {
                    Objects.requireNonNull(amountReady, "Amount allocated is required.");
                    this.amountAllocated = amountAllocated;
                    return this;
                }

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

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

                @Nullable
                public DiAmount getAmountReady() {
                    return amountReady;
                }

                @Nullable
                public DiAmount getAmountAllocated() {
                    return amountAllocated;
                }

                @NotNull
                public Change build() {
                    Objects.requireNonNull(resourceKey, "Resource key is required.");
                    Objects.requireNonNull(amountReady, "Amount ready is required.");
                    return new Change(resourceKey, segmentKeys, amountReady, amountAllocated);
                }

                @Override
                public boolean equals(final Object o) {
                    if (this == o) {
                        return true;
                    }
                    if (o == null || getClass() != o.getClass()) {
                        return false;
                    }
                    final Builder builder = (Builder) o;
                    return Objects.equals(resourceKey, builder.resourceKey) &&
                            segmentKeys.equals(builder.segmentKeys) &&
                            Objects.equals(amountReady, builder.amountReady) &&
                            Objects.equals(amountAllocated, builder.amountAllocated);
                }

                @Override
                public int hashCode() {
                    return Objects.hash(resourceKey, segmentKeys, amountReady, amountAllocated);
                }

                @Override
                public String toString() {
                    return "Builder{" +
                            "resourceKey='" + resourceKey + '\'' +
                            ", segmentKeys=" + segmentKeys +
                            ", amountReady=" + amountReady +
                            ", amountAllocated=" + amountAllocated +
                            '}';
                }

            }

        }

        public static final class Builder {

            private long requestId;
            @NotNull
            private final List<Change> changes = new ArrayList<>();
            @Nullable
            private String comment;

            private Builder() {
            }

            @NotNull
            public Builder requestId(final long requestId) {
                this.requestId = requestId;
                return this;
            }

            @NotNull
            public Builder addChange(@NotNull final Change change) {
                Objects.requireNonNull(change, "Change is required.");
                this.changes.add(change);
                return this;
            }

            @NotNull
            public Builder comment(@Nullable final String comment) {
                this.comment = comment;
                return this;
            }

            public long getRequestId() {
                return requestId;
            }

            @NotNull
            public List<Change> getChanges() {
                return changes;
            }

            @Nullable
            public String getComment() {
                return comment;
            }

            @NotNull
            public Update build() {
                return new Update(requestId, changes, comment);
            }

            @Override
            public boolean equals(final Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }
                final Builder builder = (Builder) o;
                return requestId == builder.requestId &&
                        changes.equals(builder.changes) &&
                        Objects.equals(comment, builder.comment);
            }

            @Override
            public int hashCode() {
                return Objects.hash(requestId, changes, comment);
            }

            @Override
            public String toString() {
                return "Builder{" +
                        "requestId=" + requestId +
                        ", changes=" + changes +
                        ", comment='" + comment + '\'' +
                        '}';
            }

        }

    }

    public static final class Builder {

        @NotNull
        private final List<Update> updates = new ArrayList<>();

        private Builder() {
        }

        @NotNull
        public Builder addUpdate(@NotNull final Update update) {
            Objects.requireNonNull(update, "Update is required.");
            updates.add(update);
            return this;
        }

        @NotNull
        public List<Update> getUpdates() {
            return updates;
        }

        @NotNull
        public DistributedQuota build() {
            return new DistributedQuota(updates);
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            final Builder builder = (Builder) o;
            return updates.equals(builder.updates);
        }

        @Override
        public int hashCode() {
            return Objects.hash(updates);
        }

        @Override
        public String toString() {
            return "Builder{" +
                    "updates=" + updates +
                    '}';
        }

    }

}
