package pl.allegro.tech.hermes.api;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.google.common.base.MoreObjects;
import jakarta.validation.constraints.Min;
import pl.allegro.tech.hermes.api.helpers.Patch;

import java.util.Map;
import java.util.Objects;

public class BatchSubscriptionPolicy {

    private static final int DEFAULT_MESSAGE_TTL = 60;
    private static final int DEFAULT_MESSAGE_BACKOFF = 500;
    private static final int DEFAULT_REQUEST_TIMEOUT = 30 * 1000;
    private static final int DEFAULT_BATCH_SIZE = 100;
    private static final int DEFAULT_BATCH_TIME = 30 * 1000;
    private static final int DEFAULT_BATCH_VOLUME = 64 * 1000;

    @Min(0)
    private int messageTtl;

    private boolean retryClientErrors;

    @Min(0)
    private int messageBackoff;

    @Min(1)
    private int requestTimeout;

    @Min(1)
    private int batchSize;

    @Min(1)
    private int batchTime;

    @Min(1)
    private int batchVolume;

    private BatchSubscriptionPolicy() {}

    public BatchSubscriptionPolicy(int messageTtl,
                                   boolean retryClientErrors,
                                   int messageBackoff,
                                   int requestTimeout,
                                   int batchSize,
                                   int batchTime,
                                   int batchVolume) {
        this.messageTtl = messageTtl;
        this.retryClientErrors = retryClientErrors;
        this.messageBackoff = messageBackoff;
        this.requestTimeout = requestTimeout;
        this.batchSize = batchSize;
        this.batchTime = batchTime;
        this.batchVolume = batchVolume;
    }

    @JsonCreator
    public static BatchSubscriptionPolicy create(Map<String, Object> properties) {
        return new BatchSubscriptionPolicy(
                (Integer) properties.getOrDefault("messageTtl", DEFAULT_MESSAGE_TTL),
                (Boolean) properties.getOrDefault("retryClientErrors", false),
                (Integer) properties.getOrDefault("messageBackoff", DEFAULT_MESSAGE_BACKOFF),
                (Integer) properties.getOrDefault("requestTimeout", DEFAULT_REQUEST_TIMEOUT),
                (Integer) properties.getOrDefault("batchSize", DEFAULT_BATCH_SIZE),
                (Integer) properties.getOrDefault("batchTime", DEFAULT_BATCH_TIME),
                (Integer) properties.getOrDefault("batchVolume", DEFAULT_BATCH_VOLUME)
        );
    }

    @Override
    public int hashCode() {
        return Objects.hash(messageTtl, retryClientErrors, messageBackoff, requestTimeout, batchSize, batchTime, batchVolume);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        final BatchSubscriptionPolicy other = (BatchSubscriptionPolicy) obj;
        return Objects.equals(this.messageTtl, other.messageTtl)
                && Objects.equals(this.retryClientErrors, other.retryClientErrors)
                && Objects.equals(this.messageBackoff, other.messageBackoff)
                && Objects.equals(this.requestTimeout, other.requestTimeout)
                && Objects.equals(this.batchSize, other.batchSize)
                && Objects.equals(this.batchTime, other.batchTime)
                && Objects.equals(this.batchVolume, other.batchVolume);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("messageTtl", messageTtl)
                .add("messageBackoff", messageBackoff)
                .add("retryClientErrors", retryClientErrors)
                .add("batchSize", batchSize)
                .add("batchTime", batchTime)
                .add("batchVolume", batchVolume)
                .add("requestTimeout", requestTimeout)
                .toString();
    }

    public Integer getMessageTtl() {
        return messageTtl;
    }

    public Integer getMessageBackoff() {
        return messageBackoff;
    }

    public Boolean isRetryClientErrors() {
        return retryClientErrors;
    }

    public Integer getBatchSize() {
        return batchSize;
    }

    public Integer getBatchTime() {
        return batchTime;
    }

    public Integer getBatchVolume() {
        return batchVolume;
    }

    public Integer getRequestTimeout() {
        return requestTimeout;
    }

    public static class Builder {

        private BatchSubscriptionPolicy subscriptionPolicy;

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

        public Builder() {
            subscriptionPolicy = new BatchSubscriptionPolicy();
        }

        public Builder withMessageTtl(int messageTtl) {
            subscriptionPolicy.messageTtl = messageTtl;
            return this;
        }

        public Builder withMessageBackoff(int messageBackoff) {
            subscriptionPolicy.messageBackoff = messageBackoff;
            return this;
        }

        public Builder withClientErrorRetry(boolean retryClientErrors) {
            subscriptionPolicy.retryClientErrors = retryClientErrors;
            return this;
        }

        public Builder withBatchSize(int batchSize) {
            subscriptionPolicy.batchSize = batchSize;
            return this;
        }

        public Builder withBatchTime(int batchTime) {
            subscriptionPolicy.batchTime = batchTime;
            return this;
        }

        public Builder withBatchVolume(int batchVolume) {
            subscriptionPolicy.batchVolume = batchVolume;
            return this;
        }

        public Builder withRequestTimeout(int requestTimeout) {
            subscriptionPolicy.requestTimeout = requestTimeout;
            return this;
        }

        public BatchSubscriptionPolicy build() {
            return new BatchSubscriptionPolicy(
                    subscriptionPolicy.messageTtl,
                    subscriptionPolicy.retryClientErrors,
                    subscriptionPolicy.messageBackoff,
                    subscriptionPolicy.requestTimeout,
                    subscriptionPolicy.batchSize,
                    subscriptionPolicy.batchTime,
                    subscriptionPolicy.batchVolume);
        }

        public Builder applyDefaults() {
            return this;
        }

        public <T> Builder applyPatch(PatchData patch) {
            if (patch != null) {
                subscriptionPolicy = Patch.apply(subscriptionPolicy, patch);
            }
            return this;
        }
    }
}
