package ru.yandex.qe.dispenser.domain.d;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
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.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Provide operation dto;
 *
 * @author Ruslan Kadriev <aqru@yandex-team.ru>
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProvideOperationDto {
    @NotNull
    private final String operationId;
    @Nullable
    private final String accountId;
    @NotNull
    private final String providerId;
    @NotNull
    private final List<ProvideRequestedQuota> requestedQuotas;
    @NotNull
    private final Instant createDateTime;
    @Nullable
    private final Instant updateDateTime;
    @NotNull
    private final ProvideOperationStatusDto status;
    @Nullable
    private final String errorMessage;
    @Nullable
    private final ProvideOperationErrorKindDto errorKind;
    @Nullable
    private final DeliveryAndProvideMetaRequestDto meta;
    @Nullable
    private final Set<DeliverableFolderOperationDto> folderOperationLogs;

    @JsonCreator
    public ProvideOperationDto(
            @JsonProperty(value = "operationId", required = true) @NotNull String operationId,
            @JsonProperty(value = "accountId") @Nullable String accountId,
            @JsonProperty(value = "providerId", required = true) @NotNull String providerId,
            @JsonProperty(value = "requestedQuotas", required = true) @NotNull List<ProvideRequestedQuota> requestedQuotas,
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
            @JsonProperty(value = "createDateTime", required = true) @NotNull Instant createDateTime,
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
            @JsonProperty("updateDateTime") @Nullable Instant updateDateTime,
            @JsonProperty(value = "status", required = true) @NotNull ProvideOperationStatusDto status,
            @JsonProperty("errorMessage") @Nullable String errorMessage,
            @JsonProperty("errorKind") @Nullable ProvideOperationErrorKindDto errorKind,
            @JsonProperty(value = "meta") @Nullable DeliveryAndProvideMetaRequestDto meta,
            @JsonProperty(value = "folderOperationLogs") @Nullable Set<DeliverableFolderOperationDto> folderOperationLogs
    ) {
        this.operationId = operationId;
        this.accountId = accountId;
        this.providerId = providerId;
        this.requestedQuotas = requestedQuotas;
        this.createDateTime = createDateTime;
        this.updateDateTime = updateDateTime;
        this.status = status;
        this.errorMessage = errorMessage;
        this.errorKind = errorKind;
        this.meta = meta;
        this.folderOperationLogs = folderOperationLogs;
    }

    @NotNull
    public String getOperationId() {
        return operationId;
    }

    @Nullable
    public String getAccountId() {
        return accountId;
    }

    @NotNull
    public String getProviderId() {
        return providerId;
    }

    @NotNull
    public List<ProvideRequestedQuota> getRequestedQuotas() {
        return requestedQuotas;
    }

    @NotNull
    public Instant getCreateDateTime() {
        return createDateTime;
    }

    @Nullable
    public Instant getUpdateDateTime() {
        return updateDateTime;
    }

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

    @Nullable
    public String getErrorMessage() {
        return errorMessage;
    }

    @Nullable
    public ProvideOperationErrorKindDto getErrorKind() {
        return errorKind;
    }

    @Nullable
    public DeliveryAndProvideMetaRequestDto getMeta() {
        return meta;
    }

    @Nullable
    public Set<DeliverableFolderOperationDto> getFolderOperationLog() {
        return folderOperationLogs;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        ProvideOperationDto that = (ProvideOperationDto) o;
        return operationId.equals(that.operationId) && Objects.equals(accountId, that.accountId)
                && providerId.equals(that.providerId) && requestedQuotas.equals(that.requestedQuotas)
                && createDateTime.equals(that.createDateTime) && Objects.equals(updateDateTime, that.updateDateTime)
                && status == that.status && Objects.equals(errorMessage, that.errorMessage)
                && errorKind == that.errorKind && Objects.equals(meta, that.meta)
                && Objects.equals(folderOperationLogs, that.folderOperationLogs);
    }

    @Override
    public int hashCode() {
        return Objects.hash(operationId, accountId, providerId, requestedQuotas, createDateTime, updateDateTime,
                status, errorMessage, errorKind, meta, folderOperationLogs);
    }

    @Override
    public String toString() {
        return "ProvideOperationDto{" +
                "operationId='" + operationId + '\'' +
                ", accountId='" + accountId + '\'' +
                ", providerId='" + providerId + '\'' +
                ", requestedQuotas=" + requestedQuotas +
                ", createDateTime=" + createDateTime +
                ", updateDateTime=" + updateDateTime +
                ", status=" + status +
                ", errorMessage='" + errorMessage + '\'' +
                ", errorKind=" + errorKind +
                ", meta=" + meta +
                ", folderOperationLogs=" + folderOperationLogs +
                '}';
    }

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

    public static class Builder {
        private String operationId;
        private String accountId;
        private String providerId;
        private List<ProvideRequestedQuota> requestedQuotas = new ArrayList<>();
        private Instant createDateTime;
        private Instant updateDateTime;
        private ProvideOperationStatusDto status;
        private String errorMessage;
        private ProvideOperationErrorKindDto errorKind;
        private DeliveryAndProvideMetaRequestDto meta;
        private Set<DeliverableFolderOperationDto> folderOperationLogs;

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

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

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

        public Builder requestedQuota(ProvideRequestedQuota requestedQuota) {
            requestedQuotas.add(requestedQuota);
            return this;
        }

        public Builder requestedQuotas(List<ProvideRequestedQuota> requestedQuotas) {
            this.requestedQuotas = requestedQuotas;
            return this;
        }

        public Builder createDateTime(Instant createDateTime) {
            this.createDateTime = createDateTime;
            return this;
        }

        public Builder updateDateTime(Instant updateDateTime) {
            this.updateDateTime = updateDateTime;
            return this;
        }

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

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

        public Builder errorKind(ProvideOperationErrorKindDto errorKind) {
            this.errorKind = errorKind;
            return this;
        }

        public Builder meta(DeliveryAndProvideMetaRequestDto meta) {
            this.meta = meta;
            return this;
        }

        public Builder addFolderOperationLog(DeliverableFolderOperationDto folderOperationLog) {
            if (this.folderOperationLogs == null) {
                this.folderOperationLogs = new HashSet<>();
            }
            this.folderOperationLogs.add(folderOperationLog);
            return this;
        }

        public Builder addFolderOperationLogs(Collection<DeliverableFolderOperationDto> folderOperationLogs) {
            if (this.folderOperationLogs == null) {
                this.folderOperationLogs = new HashSet<>();
            }
            this.folderOperationLogs.addAll(folderOperationLogs);
            return this;
        }

        public ProvideOperationDto build() {
            return new ProvideOperationDto(
                    Objects.requireNonNull(operationId, "OperationId is required!"),
                    accountId,
                    Objects.requireNonNull(providerId, "ProviderId is required!"),
                    Objects.requireNonNull(requestedQuotas, "RequestedQuotas is required!"),
                    Objects.requireNonNull(createDateTime, "CreateDateTime is required!"),
                    updateDateTime,
                    Objects.requireNonNull(status, "Status is required!"),
                    errorMessage,
                    errorKind,
                    meta,
                    folderOperationLogs
            );
        }
    }
}
