package ru.yandex.qe.dispenser.ws.quota.request.unbalanced;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.v1.DiAmount;
import ru.yandex.qe.dispenser.api.v1.request.unbalance.DiQuotaChangeRequestUnbalancedResult;

/**
 * Result of quota request unbalance calculation.
 *
 * @author Ruslan Kadriev <aqru@yandex-team.ru>
 */
public class QuotaChangeRequestUnbalancedResult {
    private final long requestId;
    @NotNull
    private final String providerKey;
    private final boolean unbalanced;
    @NotNull
    private final List<Change> changes;

    public QuotaChangeRequestUnbalancedResult(long requestId, @NotNull String providerKey, boolean unbalanced,
                                              @NotNull List<Change> changes) {
        this.requestId = requestId;
        this.providerKey = providerKey;
        this.unbalanced = unbalanced;
        this.changes = changes;
    }

    public long getRequestId() {
        return requestId;
    }

    @NotNull
    public String getProviderKey() {
        return providerKey;
    }

    public boolean isUnbalanced() {
        return unbalanced;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        QuotaChangeRequestUnbalancedResult that = (QuotaChangeRequestUnbalancedResult) o;
        return requestId == that.requestId && unbalanced == that.unbalanced
                && providerKey.equals(that.providerKey) && changes.equals(that.changes);
    }

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

    @Override
    public String toString() {
        return "QuotaChangeRequestUnbalancedResult{" +
                "requestId=" + requestId +
                ", providerKey='" + providerKey + '\'' +
                ", unbalanced=" + unbalanced +
                ", changes=" + changes +
                '}';
    }

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

    public DiQuotaChangeRequestUnbalancedResult toView() {
        List<DiQuotaChangeRequestUnbalancedResult.DiChange> changeList = changes.stream()
                .map(change -> {
                    ResourceKey resourceKey1 = change.getResourceKey();
                    DiQuotaChangeRequestUnbalancedResult.DiResourceKey resourceKey =
                            DiQuotaChangeRequestUnbalancedResult.DiResourceKey.builder()
                                    .resourceKey(resourceKey1.getResourceKey())
                                    .orderId(resourceKey1.getOrderId())
                                    .segmentKeys(resourceKey1.getSegmentKeys().stream()
                                            .map(this::toDiSegment)
                                            .collect(Collectors.toSet()))
                            .build();
                    return DiQuotaChangeRequestUnbalancedResult.DiChange.builder()
                            .resourceKey(resourceKey)
                            .recommendedMax(change.getRecommendedMax())
                            .recommendedMin(change.getRecommendedMin())
                            .build();
                })
                .collect(Collectors.toList());
        return DiQuotaChangeRequestUnbalancedResult.builder()
                .providerKey(providerKey)
                .unbalanced(unbalanced)
                .changes(changeList)
                .build();
    }

    private DiQuotaChangeRequestUnbalancedResult.DiSegmentKey toDiSegment(SegmentKey segmentKey) {
        return DiQuotaChangeRequestUnbalancedResult.DiSegmentKey.builder()
                .segmentationKey(segmentKey.getSegmentationKey())
                .segmentKey(segmentKey.getSegmentKey())
                .build();
    }

    public static class Builder {
        private Long requestId;
        private String providerKey;
        private Boolean unbalanced;
        private List<Change> changes;

        public Builder requestId(Long requestId) {
            this.requestId = requestId;
            return this;
        }

        public Builder providerKey(String providerKey) {
            this.providerKey = providerKey;
            return this;
        }

        public Builder unbalanced(Boolean unbalanced) {
            this.unbalanced = unbalanced;
            return this;
        }

        public Builder changes(List<Change> changes) {
            this.changes = changes;
            return this;
        }

        public QuotaChangeRequestUnbalancedResult build() {
            return new QuotaChangeRequestUnbalancedResult(
                    Objects.requireNonNull(requestId),
                    Objects.requireNonNull(providerKey),
                    Objects.requireNonNull(unbalanced),
                    Objects.requireNonNull(changes)
            );
        }
    }

    public static class Change {
        @NotNull
        private final ResourceKey resourceKey;
        @Nullable
        private final DiAmount recommendedMax;
        @Nullable
        private final DiAmount recommendedMin;

        public Change(@NotNull ResourceKey resourceKey, @Nullable DiAmount recommendedMax,
                      @Nullable DiAmount recommendedMin) {
            this.resourceKey = resourceKey;
            this.recommendedMax = recommendedMax;
            this.recommendedMin = recommendedMin;
        }

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

        @Nullable
        public DiAmount getRecommendedMax() {
            return recommendedMax;
        }

        @Nullable
        public DiAmount getRecommendedMin() {
            return recommendedMin;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Change change = (Change) o;
            return resourceKey.equals(change.resourceKey) && Objects.equals(recommendedMax, change.recommendedMax)
                    && Objects.equals(recommendedMin, change.recommendedMin);
        }

        @Override
        public int hashCode() {
            return Objects.hash(resourceKey, recommendedMax, recommendedMin);
        }

        @Override
        public String toString() {
            return "Change{" +
                    "resourceKey=" + resourceKey +
                    ", recommendedMax=" + recommendedMax +
                    ", recommendedMin=" + recommendedMin +
                    '}';
        }

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

        public static class Builder {
            private ResourceKey resourceKey;
            private DiAmount recommendedMax;
            private DiAmount recommendedMin;

            public Builder resourceKey(ResourceKey resourceKey) {
                this.resourceKey = resourceKey;
                return this;
            }

            public Builder recommendedMax(DiAmount recommendedMax) {
                this.recommendedMax = recommendedMax;
                return this;
            }

            public Builder recommendedMin(DiAmount recommendedMin) {
                this.recommendedMin = recommendedMin;
                return this;
            }

            public Change build() {
                return new Change(
                        Objects.requireNonNull(resourceKey),
                        recommendedMax,
                        recommendedMin
                );
            }
        }
    }

    public static class ResourceKey {
        @NotNull
        private final String resourceKey;
        private final long orderId;
        @NotNull
        private final Set<SegmentKey> segmentKeys;

        public ResourceKey(@NotNull String resourceKey, long orderId, @NotNull Set<SegmentKey> segmentKeys) {
            this.resourceKey = resourceKey;
            this.orderId = orderId;
            this.segmentKeys = segmentKeys;
        }

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

        public long getOrderId() {
            return orderId;
        }

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

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ResourceKey that = (ResourceKey) o;
            return orderId == that.orderId && Objects.equals(resourceKey, that.resourceKey)
                    && Objects.equals(segmentKeys, that.segmentKeys);
        }

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

        @Override
        public String toString() {
            return "ResourceKey{" +
                    "resourceKey='" + resourceKey + '\'' +
                    ", orderId=" + orderId +
                    ", segmentKeys=" + segmentKeys +
                    '}';
        }

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

        public static class Builder {
            private String resourceKey;
            private Long orderId;
            private Set<SegmentKey> segmentKeys;

            public Builder resourceKey(String resourceKey) {
                this.resourceKey = resourceKey;
                return this;
            }

            public Builder orderId(Long orderId) {
                this.orderId = orderId;
                return this;
            }

            public Builder segmentKeys(Set<SegmentKey> segmentKeys) {
                this.segmentKeys = segmentKeys;
                return this;
            }

            public ResourceKey build() {
                return new ResourceKey(
                        Objects.requireNonNull(resourceKey),
                        Objects.requireNonNull(orderId),
                        Objects.requireNonNull(segmentKeys)
                );
            }
        }
    }
}
