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

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.util.ValidationUtils;

public class DiQuotaChangeRequest {
    private final long id;
    @NotNull
    private final DiProject project;
    @Nullable
    private final String description;
    @Nullable
    private final String comment;
    @Nullable
    private final String calculations;
    @NotNull
    private final String author;
    @Nullable
    private final String responsible;
    private final long created;
    private final long updated;
    @NotNull
    private final Status status;
    @NotNull
    private final Type type;
    @Nullable
    private final String trackerIssueKey;
    @NotNull
    private final List<Change> changes;
    @Nullable
    private final DiProject sourceProject;
    @NotNull
    private final Set<DiQuotaChangeRequest.Permission> permissions;
    @NotNull
    private final List<String> chartLinks;
    @Nullable
    private final String chartLinksAbsenceExplanation;
    @NotNull
    private final List<Long> botPreOrderIds;
    @NotNull
    private final Map<String, String> additionalProperties;
    @Nullable
    private final Campaign campaign;
    @Nullable
    private final DiResourcePreorderReasonType resourcePreorderReasonType;
    @Nullable
    private final DiGoal goal;
    private final boolean readyForAllocation;
    @Nullable
    private final Map<Long, String> requestGoalAnswers;
    @Nullable
    private final Double cost;
    @Nullable
    private final String botPreOrdersUrl;
    @Nullable
    private final String summary;
    @NotNull
    private final Set<ProviderAllocationInfo> providersToAllocate;
    private final Set<BaseResourceChange> baseResourceChanges;
    private final ExtraReportFields extraReportFields;
    private final boolean showAllocationNote;
    @Nullable
    private final String requestOwningCost;
    @Nullable
    private final String relativeCost;
    @Nullable
    private final String percentageOfCampaignOwningCost;
    private final boolean importantRequest;
    private final boolean unbalanced;

    @SuppressWarnings({"ConstructorWithTooManyParameters"})
    @JsonCreator
    public DiQuotaChangeRequest(@JsonProperty("id") final long id,
                                @NotNull @JsonProperty("project") final DiProject project,
                                @NotNull @JsonProperty("changes") final List<Change> changes,
                                @Nullable @JsonProperty("description") final String description,
                                @Nullable @JsonProperty("comment") final String comment,
                                @Nullable @JsonProperty("calculations") final String calculations,
                                @NotNull @JsonProperty("author") final String author,
                                @Nullable @JsonProperty("responsible") final String responsible,
                                @JsonProperty("created") final long created,
                                @JsonProperty("updated") final long updated,
                                @NotNull @JsonProperty("status") final Status status,
                                @NotNull @JsonProperty("type") final Type type,
                                @Nullable @JsonProperty("trackerIssueKey") final String trackerIssueKey,
                                @Nullable @JsonProperty("sourceProject") final DiProject sourceProject,
                                @NotNull @JsonProperty("permissions") final Set<Permission> permissions,
                                @NotNull @JsonProperty("chartLinks") final List<String> chartLinks,
                                @Nullable @JsonProperty("chartLinksAbsenceExplanation") final String chartLinksAbsenceExplanation,
                                @NotNull @JsonProperty("botPreOrderIds") final List<Long> botPreOrderIds,
                                @NotNull @JsonProperty("additionalProperties") final Map<String, String> additionalProperties,
                                @Nullable @JsonProperty("campaign") final Campaign campaign,
                                @Nullable @JsonProperty("resourcePreorderReasonType") final DiResourcePreorderReasonType resourcePreorderReasonType,
                                @Nullable @JsonProperty("goal") final DiGoal goal,
                                @JsonProperty("readyForAllocation") final boolean readyForAllocation,
                                @Nullable @JsonProperty("requestGoalAnswers") final Map<Long, String> requestGoalAnswers,
                                @Nullable @JsonProperty("botPreOrdersUrl") final String botPreOrdersUrl,
                                @Nullable @JsonProperty("cost") final Double cost,
                                @Nullable @JsonProperty("requestOwningCost") final String requestOwningCost,
                                @Nullable @JsonProperty("relativeCost") final String relativeCost,
                                @Nullable @JsonProperty("percentageOfCampaignOwningCost") final String percentageOfCampaignOwningCost,
                                @Nullable @JsonProperty("summary") final String summary,
                                @NotNull @JsonProperty("providersToAllocate") final Set<ProviderAllocationInfo> providersToAllocate,
                                @JsonProperty("baseResourceChanges") Set<BaseResourceChange> baseResourceChanges,
                                @JsonProperty("extraReportFields") ExtraReportFields extraReportFields,
                                @JsonProperty("showAllocationNote") boolean showAllocationNote,
                                @JsonProperty("importantRequest") boolean importantRequest,
                                @JsonProperty("unbalanced") boolean unbalanced) {
        this.id = id;
        this.project = project;
        this.changes = changes;
        this.description = description;
        this.comment = comment;
        this.calculations = calculations;
        this.author = author;
        this.responsible = responsible;
        this.created = created;
        this.updated = updated;
        this.status = status;
        this.type = type;
        this.trackerIssueKey = trackerIssueKey;
        this.sourceProject = sourceProject;
        this.permissions = permissions;
        this.chartLinks = chartLinks;
        this.chartLinksAbsenceExplanation = chartLinksAbsenceExplanation;
        this.botPreOrderIds = botPreOrderIds;
        this.additionalProperties = additionalProperties;
        this.campaign = campaign;
        this.resourcePreorderReasonType = resourcePreorderReasonType;
        this.goal = goal;
        this.readyForAllocation = readyForAllocation;
        this.requestGoalAnswers = requestGoalAnswers;
        this.botPreOrdersUrl = botPreOrdersUrl;
        this.cost = cost;
        this.requestOwningCost = requestOwningCost;
        this.relativeCost = relativeCost;
        this.percentageOfCampaignOwningCost = percentageOfCampaignOwningCost;
        this.summary = summary;
        this.providersToAllocate = providersToAllocate;
        this.baseResourceChanges = baseResourceChanges;
        this.extraReportFields = extraReportFields;
        this.showAllocationNote = showAllocationNote;
        this.importantRequest = importantRequest;
        this.unbalanced = unbalanced;
    }

    private DiQuotaChangeRequest(final Builder builder) {
        id = builder.id;
        project = ValidationUtils.requireNonNull(builder.project, "Project is required");
        changes = ValidationUtils.requireNonNull(builder.changes, "Changes are required");
        description = builder.description;
        comment = builder.comment;
        calculations = builder.calculations;
        author = ValidationUtils.requireNonNull(builder.author, "Author is required");
        responsible = builder.responsible;
        created = ValidationUtils.requireNonNull(builder.created, "Created is required");
        updated = ValidationUtils.requireNonNull(builder.updated, "Updated is required");
        status = ValidationUtils.requireNonNull(builder.status, "Status is required");
        type = ValidationUtils.requireNonNull(builder.type, "Type is required");
        trackerIssueKey = builder.trackerIssueKey;
        sourceProject = builder.sourceProject;
        permissions = builder.permissions != null ? builder.permissions : Collections.emptySet();
        chartLinks = ValidationUtils.requireNonNull(builder.chartLinks, "Chart links are required");
        chartLinksAbsenceExplanation = builder.chartLinksAbsenceExplanation;
        botPreOrderIds = builder.botPreOrderIds;
        additionalProperties = builder.additionalProperties;
        campaign = builder.campaign;
        resourcePreorderReasonType = builder.resourcePreorderReasonType;
        goal = builder.goal;
        readyForAllocation = ValidationUtils.requireNonNull(builder.readyForAllocation, "ReadyForAllocation is required");
        requestGoalAnswers = builder.requestGoalAnswers;
        botPreOrdersUrl = builder.botPreOrdersUrl;
        cost = builder.cost;
        requestOwningCost = builder.requestOwningCost;
        relativeCost = builder.relativeCost;
        percentageOfCampaignOwningCost = builder.percentageOfCampaignOwningCost;
        summary = builder.summary;
        providersToAllocate = builder.providersToAllocate;
        baseResourceChanges = builder.baseResourceChanges;
        extraReportFields = builder.extraReportFields;
        showAllocationNote = builder.showAllocationNote != null ? builder.showAllocationNote : false;
        importantRequest = Objects.requireNonNull(builder.importantRequest);
        unbalanced = Objects.requireNonNull(builder.unbalanced);
    }

    public long getId() {
        return id;
    }

    @NotNull
    public DiProject getProject() {
        return project;
    }

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

    @Nullable
    public String getDescription() {
        return description;
    }

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

    @Nullable
    public String getCalculations() {
        return calculations;
    }

    @NotNull
    public String getAuthor() {
        return author;
    }

    @Nullable
    public String getResponsible() {
        return responsible;
    }

    public long getCreated() {
        return created;
    }

    public long getUpdated() {
        return updated;
    }

    @NotNull
    public Status getStatus() {
        return status;
    }

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

    @Nullable
    public DiProject getSourceProject() {
        return sourceProject;
    }

    @Nullable
    public String getTrackerIssueKey() {
        return trackerIssueKey;
    }

    @NotNull
    public Set<DiQuotaChangeRequest.Permission> getPermissions() {
        return permissions;
    }

    @NotNull
    public List<String> getChartLinks() {
        return chartLinks;
    }

    @Nullable
    public String getChartLinksAbsenceExplanation() {
        return chartLinksAbsenceExplanation;
    }

    @NotNull
    public List<Long> getBotPreOrderIds() {
        return botPreOrderIds;
    }

    @NotNull
    public Map<String, String> getAdditionalProperties() {
        return additionalProperties;
    }

    @Nullable
    public Campaign getCampaign() {
        return campaign;
    }

    @Nullable
    public DiResourcePreorderReasonType getResourcePreorderReasonType() {
        return resourcePreorderReasonType;
    }

    @Nullable
    public DiGoal getGoal() {
        return goal;
    }

    public boolean isReadyForAllocation() {
        return readyForAllocation;
    }

    @Nullable
    public Map<Long, String> getRequestGoalAnswers() {
        return requestGoalAnswers;
    }

    @Nullable
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public Double getCost() {
        return cost;
    }

    @Nullable
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public String getRequestOwningCost() {
        return requestOwningCost;
    }

    @Nullable
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public String getRelativeCost() {
        return relativeCost;
    }

    @Nullable
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public String getPercentageOfCampaignOwningCost() {
        return percentageOfCampaignOwningCost;
    }

    @Nullable
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public String getBotPreOrdersUrl() {
        return botPreOrdersUrl;
    }

    @Nullable
    public String getSummary() {
        return summary;
    }

    @NotNull
    public Set<ProviderAllocationInfo> getProvidersToAllocate() {
        return providersToAllocate;
    }

    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    public Set<BaseResourceChange> getBaseResourceChanges() {
        return baseResourceChanges;
    }

    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    public Optional<ExtraReportFields> getExtraReportFields() {
        return Optional.ofNullable(extraReportFields);
    }

    public boolean isShowAllocationNote() {
        return showAllocationNote;
    }

    public boolean isImportantRequest() {
        return importantRequest;
    }

    public boolean isUnbalanced() {
        return unbalanced;
    }

    public enum Status {
        NEW,
        CANCELLED,
        REJECTED,
        APPLIED,
        CONFIRMED,
        READY_FOR_REVIEW,
        COMPLETED,
        NEED_INFO,
        APPROVED,
    }

    public enum Permission {
        CAN_REOPEN,
        CAN_CANCEL,
        CAN_APPLY,
        CAN_CONFIRM,
        CAN_REJECT,
        CAN_EDIT,
        CAN_MARK_AS_READY_FOR_REVIEW,
        CAN_COMPLETE,
        CAN_EDIT_REVIEW_POPUP,
        CAN_ALLOCATE_QUOTA,
        CAN_VIEW_BOT_PREORDER_COSTS,
        CAN_VIEW_OWNING_COST,
        CAN_ALLOCATE_QUOTA_IN_YP,
        CAN_APPROVE,
        CAN_MARK_AS_NEED_INFO,
        CAN_CLOSE_ALLOCATION_NOTE,
    }

    public enum Type {
        QUOTA_INCREASE,
        QUOTA_MOVE,
        RESOURCE_PREORDER
    }

    public static class Builder {
        private final long id;
        @Nullable
        private DiProject project;
        @Nullable
        private List<Change> changes;
        @Nullable
        private String description;
        @Nullable
        public String comment;
        @Nullable
        private String calculations;
        @Nullable
        private String author;
        @Nullable
        private String responsible;
        @Nullable
        private Long created;
        @Nullable
        private Long updated;
        @Nullable
        private Status status;
        @Nullable
        private Type type;
        @Nullable
        private String trackerIssueKey;
        @Nullable
        private DiProject sourceProject;
        @Nullable
        private Set<DiQuotaChangeRequest.Permission> permissions;
        @Nullable
        private List<String> chartLinks;
        @Nullable
        private String chartLinksAbsenceExplanation;
        @NotNull
        private final List<Long> botPreOrderIds = new ArrayList<>();
        @NotNull
        private final Map<String, String> additionalProperties = new HashMap<>();
        @Nullable
        private Campaign campaign;
        @Nullable
        private DiResourcePreorderReasonType resourcePreorderReasonType;
        @Nullable
        private DiGoal goal;
        @Nullable
        public Boolean readyForAllocation;
        @Nullable
        private Map<Long, String> requestGoalAnswers;
        @Nullable
        private Double cost;
        @Nullable
        private String botPreOrdersUrl;
        @Nullable
        private String summary;
        @NotNull
        private final Set<ProviderAllocationInfo> providersToAllocate = new HashSet<>();
        private final Set<BaseResourceChange> baseResourceChanges = new HashSet<>();
        @Nullable
        private ExtraReportFields extraReportFields;
        @Nullable
        private Boolean showAllocationNote;
        @Nullable
        private String requestOwningCost;
        @Nullable
        private String relativeCost;
        @Nullable
        private String percentageOfCampaignOwningCost;
        @Nullable
        private Boolean importantRequest;
        @Nullable
        private Boolean unbalanced;

        public Builder(final long id) {
            this.id = id;
        }

        public DiQuotaChangeRequest build() {
            return new DiQuotaChangeRequest(this);
        }

        public Builder project(final DiProject project) {
            this.project = project;
            return this;
        }

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

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

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

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

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

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

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

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

        public Builder status(final Status status) {
            this.status = status;
            return this;
        }

        public Builder type(final Type type) {
            this.type = type;
            return this;
        }

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

        public Builder sourceProject(final DiProject project) {
            this.sourceProject = project;
            return this;
        }

        public Builder permissions(final Set<DiQuotaChangeRequest.Permission> permissions) {
            this.permissions = permissions;
            return this;
        }

        public Builder chartLinks(final List<String> chartLinks) {
            this.chartLinks = chartLinks;
            return this;
        }

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

        public Builder botPreOrderIds(final Collection<Long> botPreOrderIds) {
            this.botPreOrderIds.addAll(botPreOrderIds);
            return this;
        }

        public Builder additionalProperties(@Nullable final Map<String, String> additionalProperties) {
            if (additionalProperties != null) {
                this.additionalProperties.putAll(additionalProperties);
            }
            return this;
        }

        public Builder campaign(final Campaign campaign) {
            this.campaign = campaign;
            return this;
        }

        public Builder resourcePreorderReasonType(final DiResourcePreorderReasonType resourcePreorderReasonType) {
            this.resourcePreorderReasonType = resourcePreorderReasonType;
            return this;
        }

        public Builder goal(final DiGoal goal) {
            this.goal = goal;
            return this;
        }

        public Builder readyForAllocation(final boolean readyForAllocation) {
            this.readyForAllocation = readyForAllocation;
            return this;
        }

        public Builder requestGoalAnswers(final Map<Long, String> requestGoalAnswers) {
            this.requestGoalAnswers = requestGoalAnswers;
            return this;
        }

        public Builder cost(@Nullable final Double cost) {
            this.cost = cost;
            return this;
        }

        public Builder requestOwningCost(@Nullable  String requestOwningCost) {
            this.requestOwningCost = requestOwningCost;
            return this;
        }

        public Builder relativeCost(@Nullable String relativeCost) {
            this.relativeCost = relativeCost;
            return this;
        }

        public Builder percentageOfCampaignOwningCost(@Nullable  String percentageOfCampaignOwningCost) {
            this.percentageOfCampaignOwningCost = percentageOfCampaignOwningCost;
            return this;
        }

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

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

        public Builder providersToAllocate(@NotNull final Set<ProviderAllocationInfo> providersToAllocate) {
            this.providersToAllocate.addAll(providersToAllocate);
            return this;
        }

        public Builder addBaseResourceChange(BaseResourceChange baseResourceChange) {
            this.baseResourceChanges.add(baseResourceChange);
            return this;
        }

        public Builder addBaseResourceChanges(Collection<? extends BaseResourceChange> baseResourceChanges) {
            this.baseResourceChanges.addAll(baseResourceChanges);
            return this;
        }

        public Builder addBaseResourceChanges(BaseResourceChange... baseResourceChanges) {
            this.baseResourceChanges.addAll(List.of(baseResourceChanges));
            return this;
        }

        public Builder extraReportFields(ExtraReportFields extraReportFields) {
            this.extraReportFields = extraReportFields;
            return this;
        }

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

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

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

    }

    public static class Change {
        private final DiQuotaChangeRequest.Order order;
        private final Service service;
        private final Resource resource;
        private final Set<String> segmentKeys;
        private final DiAmount amount;
        private final DiAmount amountReady;
        private final DiAmount amountAllocated;
        private final DiAmount amountAllocating;
        private final String owningCost;
        private final String percentageOfRequestOwningCost;

        @SuppressWarnings("ConstructorWithTooManyParameters")
        @JsonCreator
        public Change(
                @JsonProperty("order") final Order order,
                @JsonProperty("service") final Service service,
                @JsonProperty("resource") final Resource resource,
                @JsonProperty("segments") final Set<String> segmentKeys,
                @JsonProperty("amount") final DiAmount amount,
                @JsonProperty("amountReady") final DiAmount amountReady,
                @JsonProperty("amountAllocated") final DiAmount amountAllocated,
                @JsonProperty("amountAllocating") final DiAmount amountAllocating,
                @JsonProperty("owningCost") final String owningCost,
                @JsonProperty("percentageOfRequestOwningCost") final String percentageOfRequestOwningCost) {
            this.order = order;
            this.service = service;
            this.resource = resource;
            this.segmentKeys = segmentKeys;
            this.amount = amount;
            this.amountReady = amountReady;
            this.amountAllocated = amountAllocated;
            this.amountAllocating = amountAllocating;
            this.owningCost = owningCost;
            this.percentageOfRequestOwningCost = percentageOfRequestOwningCost;
        }

        @Nullable
        public DiQuotaChangeRequest.Order getOrder() {
            return order;
        }

        public Service getService() {
            return service;
        }

        public Resource getResource() {
            return resource;
        }

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

        public DiAmount getAmount() {
            return amount;
        }

        public DiAmount getAmountReady() {
            return amountReady;
        }

        public DiAmount getAmountAllocated() {
            return amountAllocated;
        }

        public DiAmount getAmountAllocating() {
            return amountAllocating;
        }

        public String getOwningCost() {
            return owningCost;
        }

        public String getPercentageOfRequestOwningCost() {
            return percentageOfRequestOwningCost;
        }
    }

    public static class Order {
        private final long id;
        private final LocalDate orderDate;
        @SuppressWarnings("DeprecatedIsStillUsed")
        @Deprecated
        private final boolean isValid;

        @JsonCreator
        public Order(@JsonProperty("id") final long id,
                     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
                     @JsonProperty("date") final LocalDate date,
                     @Deprecated @JsonProperty("valid") final Boolean isValid) {
            this.id = id;
            this.orderDate = date;
            this.isValid = isValid;
        }

        public long getId() {
            return id;
        }

        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
        public LocalDate getOrderDate() {
            return orderDate;
        }

        @Deprecated
        public boolean isValid() {
            return isValid;
        }
    }

    public static class Campaign {

        private final Long id;
        private final String key;
        private final String name;
        private final String status;
        private final String type;

        @JsonCreator
        public Campaign(
                @JsonProperty("id") final Long id,
                @JsonProperty("key") final String key,
                @JsonProperty("name") final String name,
                @JsonProperty("status") final String status,
                @JsonProperty("type") final String type) {
            this.id = id;
            this.key = key;
            this.name = name;
            this.status = status;
            this.type = type;
        }

        public Long getId() {
            return id;
        }

        public String getKey() {
            return key;
        }

        public String getName() {
            return name;
        }

        public String getStatus() {
            return status;
        }

        public String getType() {
            return type;
        }

    }

    public static class Service {
        private final String key;
        private final String name;

        @JsonCreator
        public Service(@JsonProperty("key") final String key,
                       @JsonProperty("name") final String name) {
            this.key = key;
            this.name = name;
        }

        public String getKey() {
            return key;
        }

        public String getName() {
            return name;
        }
    }

    public static class Resource {
        private final String key;
        private final String name;

        @JsonCreator
        public Resource(@JsonProperty("key") final String key,
                        @JsonProperty("name") final String name) {
            this.key = key;
            this.name = name;
        }

        public String getKey() {
            return key;
        }

        public String getName() {
            return name;
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class BaseResourceChange {

        private final DiBotBigOrder bigOrder;
        private final BaseResource baseResource;
        private final DiAmount totalAmount;
        private final Set<PerProviderBaseResourceAmount> perProviderAmounts;

        @JsonCreator
        public BaseResourceChange(
                @JsonProperty("bigOrder") DiBotBigOrder bigOrder,
                @JsonProperty("baseResource") BaseResource baseResource,
                @JsonProperty("totalAmount") DiAmount totalAmount,
                @JsonProperty("perProviderAmounts") Set<PerProviderBaseResourceAmount> perProviderAmounts) {
            this.bigOrder = bigOrder;
            this.baseResource = baseResource;
            this.totalAmount = totalAmount;
            this.perProviderAmounts = perProviderAmounts;
        }

        public DiBotBigOrder getBigOrder() {
            return bigOrder;
        }

        public BaseResource getBaseResource() {
            return baseResource;
        }

        public DiAmount getTotalAmount() {
            return totalAmount;
        }

        public Set<PerProviderBaseResourceAmount> getPerProviderAmounts() {
            return perProviderAmounts;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BaseResourceChange that = (BaseResourceChange) o;
            return Objects.equals(bigOrder, that.bigOrder) &&
                    Objects.equals(baseResource, that.baseResource) &&
                    Objects.equals(totalAmount, that.totalAmount) &&
                    Objects.equals(perProviderAmounts, that.perProviderAmounts);
        }

        @Override
        public int hashCode() {
            return Objects.hash(bigOrder, baseResource, totalAmount, perProviderAmounts);
        }

        @Override
        public String toString() {
            return "BaseResourceChange{" +
                    "bigOrder=" + bigOrder +
                    ", baseResource=" + baseResource +
                    ", totalAmount=" + totalAmount +
                    ", perProviderAmounts=" + perProviderAmounts +
                    '}';
        }

    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class BaseResource {

        private final long id;
        private final String name;
        private final String key;
        private final Set<String> segmentKeys;
        private final BaseResourceType type;

        @JsonCreator
        public BaseResource(@JsonProperty("id") long id,
                            @JsonProperty("name") String name,
                            @JsonProperty("key") String key,
                            @JsonProperty("segmentKeys") Set<String> segmentKeys,
                            @JsonProperty("type") BaseResourceType type) {
            this.id = id;
            this.name = name;
            this.key = key;
            this.segmentKeys = segmentKeys;
            this.type = type;
        }

        public long getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public String getKey() {
            return key;
        }

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

        public BaseResourceType getType() {
            return type;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BaseResource that = (BaseResource) o;
            return id == that.id &&
                    Objects.equals(name, that.name) &&
                    Objects.equals(key, that.key) &&
                    Objects.equals(segmentKeys, that.segmentKeys) &&
                    Objects.equals(type, that.type);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, name, key, segmentKeys, type);
        }

        @Override
        public String toString() {
            return "BaseResource{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", key='" + key + '\'' +
                    ", segmentKeys=" + segmentKeys +
                    ", type=" + type +
                    '}';
        }

    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class BaseResourceType {

        private final long id;
        private final String name;
        private final String key;
        private final Service provider;

        @JsonCreator
        public BaseResourceType(@JsonProperty("id") long id,
                                @JsonProperty("name") String name,
                                @JsonProperty("key") String key,
                                @JsonProperty("provider") Service provider) {
            this.id = id;
            this.name = name;
            this.key = key;
            this.provider = provider;
        }

        public long getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public String getKey() {
            return key;
        }

        public Service getProvider() {
            return provider;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BaseResourceType that = (BaseResourceType) o;
            return id == that.id &&
                    Objects.equals(name, that.name) &&
                    Objects.equals(key, that.key) &&
                    Objects.equals(provider, that.provider);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, name, key, provider);
        }

        @Override
        public String toString() {
            return "BaseResourceType{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", key='" + key + '\'' +
                    ", provider=" + provider +
                    '}';
        }

    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class PerProviderBaseResourceAmount {

        private final Service provider;
        private final DiAmount amount;
        private final Set<Long> mappingIds;

        @JsonCreator
        public PerProviderBaseResourceAmount(@JsonProperty("provider") Service provider,
                                             @JsonProperty("amount") DiAmount amount,
                                             @JsonProperty("mappingIds") Set<Long> mappingIds) {
            this.provider = provider;
            this.amount = amount;
            this.mappingIds = mappingIds;
        }

        public Service getProvider() {
            return provider;
        }

        public DiAmount getAmount() {
            return amount;
        }

        public Set<Long> getMappingIds() {
            return mappingIds;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            PerProviderBaseResourceAmount that = (PerProviderBaseResourceAmount) o;
            return Objects.equals(provider, that.provider) &&
                    Objects.equals(amount, that.amount) &&
                    Objects.equals(mappingIds, that.mappingIds);
        }

        @Override
        public int hashCode() {
            return Objects.hash(provider, amount, mappingIds);
        }

        @Override
        public String toString() {
            return "PerProviderBaseResourceAmount{" +
                    "provider=" + provider +
                    ", amount=" + amount +
                    ", mappingIds=" + mappingIds +
                    '}';
        }

    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class ExtraReportFields {

        private final Set<String> headFirst;
        private final Set<String> headSecond;
        private final Set<String> headThird;
        private final DiProject headDepartmentFirst;
        private final DiProject headDepartmentSecond;
        private final DiProject headDepartmentThird;
        private final DiProject valueStream;
        private final Set<String> valueStreamCapacityPlanner;
        private final Set<String> valueStreamLeader;
        private final Set<String> valueStreamManager;

        @JsonCreator
        public ExtraReportFields(@JsonProperty("headFirst") Set<String> headFirst,
                                 @JsonProperty("headSecond") Set<String> headSecond,
                                 @JsonProperty("headThird") Set<String> headThird,
                                 @JsonProperty("headDepartmentFirst") DiProject headDepartmentFirst,
                                 @JsonProperty("headDepartmentSecond") DiProject headDepartmentSecond,
                                 @JsonProperty("headDepartmentThird") DiProject headDepartmentThird,
                                 @JsonProperty("valueStream") DiProject valueStream,
                                 @JsonProperty("valueStreamCapacityPlanner") Set<String> valueStreamCapacityPlanner,
                                 @JsonProperty("valueStreamLeader") Set<String> valueStreamLeader,
                                 @JsonProperty("valueStreamManager") Set<String> valueStreamManager) {
            this.headFirst = headFirst;
            this.headSecond = headSecond;
            this.headThird = headThird;
            this.headDepartmentFirst = headDepartmentFirst;
            this.headDepartmentSecond = headDepartmentSecond;
            this.headDepartmentThird = headDepartmentThird;
            this.valueStream = valueStream;
            this.valueStreamCapacityPlanner = valueStreamCapacityPlanner;
            this.valueStreamLeader = valueStreamLeader;
            this.valueStreamManager = valueStreamManager;
        }

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

        public Set<String> getHeadFirst() {
            return headFirst;
        }

        public Set<String> getHeadSecond() {
            return headSecond;
        }

        public Set<String> getHeadThird() {
            return headThird;
        }

        public Optional<DiProject> getHeadDepartmentFirst() {
            return Optional.ofNullable(headDepartmentFirst);
        }

        public Optional<DiProject> getHeadDepartmentSecond() {
            return Optional.ofNullable(headDepartmentSecond);
        }

        public Optional<DiProject> getHeadDepartmentThird() {
            return Optional.ofNullable(headDepartmentThird);
        }

        public Optional<DiProject> getValueStream() {
            return Optional.ofNullable(valueStream);
        }

        public Set<String> getValueStreamCapacityPlanner() {
            return valueStreamCapacityPlanner;
        }

        public Set<String> getValueStreamLeader() {
            return valueStreamLeader;
        }

        public Set<String> getValueStreamManager() {
            return valueStreamManager;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ExtraReportFields that = (ExtraReportFields) o;
            return Objects.equals(headFirst, that.headFirst) &&
                    Objects.equals(headSecond, that.headSecond) &&
                    Objects.equals(headThird, that.headThird) &&
                    Objects.equals(headDepartmentFirst, that.headDepartmentFirst) &&
                    Objects.equals(headDepartmentSecond, that.headDepartmentSecond) &&
                    Objects.equals(headDepartmentThird, that.headDepartmentThird) &&
                    Objects.equals(valueStream, that.valueStream) &&
                    Objects.equals(valueStreamCapacityPlanner, that.valueStreamCapacityPlanner) &&
                    Objects.equals(valueStreamLeader, that.valueStreamLeader) &&
                    Objects.equals(valueStreamManager, that.valueStreamManager);
        }

        @Override
        public int hashCode() {
            return Objects.hash(headFirst, headSecond, headThird, headDepartmentFirst, headDepartmentSecond,
                    headDepartmentThird, valueStream, valueStreamCapacityPlanner, valueStreamLeader,
                    valueStreamManager);
        }

        @Override
        public String toString() {
            return "ExtraReportFields{" +
                    "headFirst=" + headFirst +
                    ", headSecond=" + headSecond +
                    ", headThird=" + headThird +
                    ", headDepartmentFirst=" + headDepartmentFirst +
                    ", headDepartmentSecond=" + headDepartmentSecond +
                    ", headDepartmentThird=" + headDepartmentThird +
                    ", valueStream=" + valueStream +
                    ", valueStreamCapacityPlanner=" + valueStreamCapacityPlanner +
                    ", valueStreamLeader=" + valueStreamLeader +
                    ", valueStreamManager=" + valueStreamManager +
                    '}';
        }

        public static final class Builder {

            private final Set<String> headFirst = new HashSet<>();
            private final Set<String> headSecond = new HashSet<>();
            private final Set<String> headThird = new HashSet<>();
            private final Set<String> valueStreamCapacityPlanner = new HashSet<>();
            private final Set<String> valueStreamLeader = new HashSet<>();
            private final Set<String> valueStreamManager = new HashSet<>();
            private DiProject headDepartmentFirst;
            private DiProject headDepartmentSecond;
            private DiProject headDepartmentThird;
            private DiProject valueStream;

            private Builder() {
            }

            public Builder headDepartmentFirst(DiProject headDepartmentFirst) {
                this.headDepartmentFirst = headDepartmentFirst;
                return this;
            }

            public Builder headDepartmentSecond(DiProject headDepartmentSecond) {
                this.headDepartmentSecond = headDepartmentSecond;
                return this;
            }

            public Builder headDepartmentThird(DiProject headDepartmentThird) {
                this.headDepartmentThird = headDepartmentThird;
                return this;
            }

            public Builder valueStream(DiProject valueStream) {
                this.valueStream = valueStream;
                return this;
            }

            public Builder addHeadFirst(String headFirst) {
                this.headFirst.add(headFirst);
                return this;
            }

            public Builder addHeadFirst(Collection<? extends String> headFirst) {
                this.headFirst.addAll(headFirst);
                return this;
            }

            public Builder addHeadSecond(String headSecond) {
                this.headSecond.add(headSecond);
                return this;
            }

            public Builder addHeadSecond(Collection<? extends String> headSecond) {
                this.headSecond.addAll(headSecond);
                return this;
            }

            public Builder addHeadThird(String headThird) {
                this.headThird.add(headThird);
                return this;
            }

            public Builder addHeadThird(Collection<? extends String> headThird) {
                this.headThird.addAll(headThird);
                return this;
            }

            public Builder addValueStreamCapacityPlanner(String valueStreamCapacityPlanner) {
                this.valueStreamCapacityPlanner.add(valueStreamCapacityPlanner);
                return this;
            }

            public Builder addValueStreamCapacityPlanner(Collection<? extends String> valueStreamCapacityPlanner) {
                this.valueStreamCapacityPlanner.addAll(valueStreamCapacityPlanner);
                return this;
            }

            public Builder addValueStreamLeader(String valueStreamLeader) {
                this.valueStreamLeader.add(valueStreamLeader);
                return this;
            }

            public Builder addValueStreamLeader(Collection<? extends String> valueStreamLeader) {
                this.valueStreamLeader.addAll(valueStreamLeader);
                return this;
            }

            public Builder addValueStreamManager(String valueStreamManager) {
                this.valueStreamManager.add(valueStreamManager);
                return this;
            }

            public Builder addValueStreamManager(Collection<? extends String> valueStreamManager) {
                this.valueStreamManager.addAll(valueStreamManager);
                return this;
            }

            public ExtraReportFields build() {
                return new ExtraReportFields(headFirst, headSecond, headThird, headDepartmentFirst,
                        headDepartmentSecond, headDepartmentThird, valueStream, valueStreamCapacityPlanner,
                        valueStreamLeader, valueStreamManager);
            }

        }

    }

    public static final class ProviderAllocationInfo {
        private final String key;
        private final Set<DiQuotaChangeRequest.Permission> permissions;
        private final List<String> notes;
        private final boolean hasAllocatingResources;
        private final boolean deliverable;

        @JsonCreator
        public ProviderAllocationInfo(@JsonProperty("key") String key,
                                      @JsonProperty("permissions") Set<Permission> permissions,
                                      @JsonProperty("notes") List<String> notes,
                                      @JsonProperty("hasAllocatingResources") boolean hasAllocatingResources,
                                      @JsonProperty("deliverable") boolean deliverable) {
            this.key = key;
            this.permissions = permissions;
            this.notes = notes;
            this.hasAllocatingResources = hasAllocatingResources;
            this.deliverable = deliverable;
        }

        public String getKey() {
            return key;
        }

        public Set<Permission> getPermissions() {
            return permissions;
        }

        public List<String> getNotes() {
            return notes;
        }

        @JsonProperty("hasAllocatingResources")
        public boolean hasAllocatingResources() {
            return hasAllocatingResources;
        }

        public boolean isDeliverable() {
            return deliverable;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ProviderAllocationInfo that = (ProviderAllocationInfo) o;
            return hasAllocatingResources == that.hasAllocatingResources &&
                    deliverable == that.deliverable &&
                    Objects.equals(key, that.key) &&
                    Objects.equals(permissions, that.permissions) &&
                    Objects.equals(notes, that.notes);
        }

        @Override
        public int hashCode() {
            return Objects.hash(key, permissions, notes, hasAllocatingResources, deliverable);
        }

        @Override
        public String toString() {
            return "ProviderAllocationInfo{" +
                    "key='" + key + '\'' +
                    ", permissions=" + permissions +
                    ", notes=" + notes +
                    ", hasAllocatingResources=" + hasAllocatingResources +
                    ", deliverable=" + deliverable +
                    '}';
        }
    }
}
