package ru.yandex.qe.dispenser.domain;

import java.util.Set;

import javax.annotation.ParametersAreNonnullByDefault;

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.DiQuota;
import ru.yandex.qe.dispenser.api.v1.DiQuotaLightView;
import ru.yandex.qe.dispenser.domain.dao.segment.SegmentUtils;
import ru.yandex.qe.dispenser.solomon.Solomon;
import ru.yandex.qe.dispenser.domain.util.ApplicationContextProvider;

/**
 * Агрегированное представление квоты.
 * <p>В дополнение к полям, определенным в классе {@link Quota}, здесь определено поле {@code totalActual}, которое агрегирует
 * потребление квоты на подпроектах.
 *
 * @see ru.yandex.qe.dispenser.domain.dao.quota.QuotaUtils
 * @see Quota
 */
@ParametersAreNonnullByDefault
public class QuotaAggregateViewImpl implements QuotaView {
    @NotNull
    private final Quota.Key key;
    private final long max;
    private final long ownMax;
    private final long totalActual;
    private final long ownActual;

    @Nullable
    private final Long lastOverquotingTs;

    private QuotaAggregateViewImpl(@NotNull final Builder builder) {
        key = builder.key;
        max = builder.max;
        ownMax = builder.ownMax;
        ownActual = builder.ownActual;
        totalActual = builder.totalActual;
        lastOverquotingTs = builder.lastOverquotingTs;
    }

    public QuotaAggregateViewImpl(@NotNull final Quota quota, final long totalActual, final long max) {
        key = quota.getKey();
        this.max = max;
        ownMax = quota.getOwnMax();
        ownActual = quota.getOwnActual();
        this.totalActual = totalActual;
        lastOverquotingTs = quota.getLastOverquotingTs();
    }

    public QuotaAggregateViewImpl(@NotNull final Quota quota, final long totalActual) {
        this(quota, totalActual, quota.getMax());
    }

    @NotNull
    public static Builder builder(final @NotNull QuotaSpec spec, final @NotNull Project project,
                                  @NotNull final Set<Segment> segments) {
        return new Builder(new Quota.Key(spec, project, segments));
    }

    @NotNull
    public static QuotaAggregateViewImpl noQuota(@NotNull final Project project, final @NotNull QuotaSpec info,
                                                 @NotNull final Set<Segment> segments) {
        return builder(info, project, segments).build();
    }

    @NotNull
    public static QuotaAggregateViewImpl noQuota(@NotNull final Quota.Key key) {
        return new Builder(key).build();
    }

    @NotNull
    public static QuotaAggregateViewImpl noQuota(@NotNull final Project project, final @NotNull QuotaSpec info) {
        return new Builder(Quota.Key.totalOf(info, project)).build();
    }

    @Override
    public long getMax() {
        return max;
    }

    @NotNull
    @Override
    public DiAmount getMaxAmount() {
        return DiAmount.of(getMax(), getResource().getType().getBaseUnit());
    }

    @NotNull
    @Override
    public QuotaSpec getSpec() {
        return key.getSpec();
    }

    @NotNull
    @Override
    public Resource getResource() {
        return getSpec().getResource();
    }

    @NotNull
    @Override
    public Project getProject() {
        return key.getProject();
    }

    @NotNull
    @Override
    public DiAmount getTotalActualAmount() {
        return DiAmount.of(getTotalActual(), getResource().getType().getBaseUnit());
    }

    @Nullable
    @Override
    public Long getLastOverquotingTs() {
        return lastOverquotingTs;
    }

    public boolean canAcquire(final long value) {
        return getTotalActual() + value <= getMax();
    }

    public int getTotalPercent() {
        return max == 0 ? getTotalActual() == 0 ? 0 : Integer.MAX_VALUE : (int) (100 * getTotalActual() / max);
    }

    @NotNull
    @Override
    public Set<Segment> getSegments() {
        return key.getSegments();
    }

    @NotNull
    @Override
    public Quota.Key getKey() {
        return key;
    }

    public boolean isAggregation() {
        return getKey().isAggregation();
    }

    @Override
    public boolean isTotal() {
        return getKey().isTotal();
    }

    @Override
    public long getEffectiveOwnMax() {
        if (key.getProject().isRealLeaf()) {
            final Service.Settings settings = key.getSpec().getResource().getService().getSettings();
            return settings.usesProjectHierarchy() ? max : ownMax;
        }
        return ownMax;
    }

    @Override
    public long getOwnMax() {
        return ownMax;
    }

    @Override
    public long getOwnActual() {
        return ownActual;
    }

    @Override
    public long getTotalActual() {
        return totalActual;
    }

    @NotNull
    public DiQuota toView() {
        return DiQuota.builder(getSpec().toView(), getProject().toView())
                .max(getMaxAmount())
                .actual(getTotalActualAmount())
                .ownMax(DiAmount.of(getEffectiveOwnMax(), getResource().getType().getBaseUnit()))
                .ownActual(DiAmount.of(getOwnActual(), getResource().getType().getBaseUnit()))
                .lastOverquotingTs(getLastOverquotingTs())
                .statisticsLink(ApplicationContextProvider.demandBean(Solomon.class).getStatisticsLink(key))
                .segments(SegmentUtils.getNonAggregationSegmentKeys(getSegments()))
                .build();
    }

    @NotNull
    public DiQuotaLightView toLightView() {
        return DiQuotaLightView.withKey(getKey().toView())
                .withMax(getMax())
                .withOwnMax(getEffectiveOwnMax())
                .withOwnActual(getOwnActual())
                .withActual(getTotalActual())
                .withLastOverquotingTs(getLastOverquotingTs())
                .build();
    }

    @Override
    public int compareTo(@NotNull final QuotaView quota) {
        int compare = getSpec().compareTo(quota.getSpec());
        if (compare == 0) {
            compare = getProject().compareTo(quota.getProject());
        }
        if (compare == 0) {
            compare = getSegments().size() - quota.getSegments().size();
        }
        if (compare == 0) {
            final Object[] o1 = getSegments().toArray();
            final Object[] o2 = quota.getSegments().toArray();
            for (int i = 0; i < o1.length && compare == 0; i++) {
                compare = ((Segment) o1[i]).compareTo((Segment) o2[i]);
            }
        }
        return compare;
    }

    @Override
    public boolean equals(@Nullable final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final QuotaAggregateViewImpl quota = (QuotaAggregateViewImpl) o;
        return key.equals(quota.key);
    }

    @Override
    public int hashCode() {
        return key.hashCode();
    }

    public static final class Builder {
        private final Quota.Key key;

        private long max;
        private long ownMax;
        private long ownActual;
        private long totalActual;

        @Nullable
        private Long lastOverquotingTs;

        private Builder(final @NotNull Quota.Key key) {
            this.key = key;
        }

        private Builder(final @NotNull Quota quota) {
            this.key = quota.getKey();
            this.max = quota.getMax();
            this.ownMax = quota.getOwnMax();
            this.ownActual = quota.getOwnActual();
            this.lastOverquotingTs = quota.getLastOverquotingTs();
        }

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


        @NotNull
        public QuotaAggregateViewImpl build() {
            return new QuotaAggregateViewImpl(this);
        }
    }
}
