package ru.yandex.qe.dispenser.domain;

import org.apache.commons.lang3.builder.CompareToBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.v1.DiQuotingMode;
import ru.yandex.qe.dispenser.api.v1.DiResource;
import ru.yandex.qe.dispenser.api.v1.DiResourceType;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.index.NormalizedPrimaryKeyBase;
import ru.yandex.qe.dispenser.domain.util.ValidationUtils;

/**
 * Квотируемый ресурс {@link Service провайдера}. Используется в {@link QuotaSpec} для определения спецификаций квот, а также в заявках
 * на ресурсы ({@link QuotaChangeRequest}).
 * <p> Ресурс имеет {@link DiResourceType тип} и {@link DiQuotingMode режим квотирования}.
 *
 * @see Entity
 * @see QuotaChangeRequest
 * @see Quota
 */
public final class Resource extends NormalizedPrimaryKeyBase<Resource.Key> {
    @NotNull
    private final String name;
    @Nullable
    private final String description;
    @NotNull
    private final DiResourceType type;
    @NotNull
    private final DiQuotingMode mode;

    @Nullable
    private final ResourceGroup group;

    @Nullable
    private final Integer priority;

    public Resource(@NotNull final Builder builder) {
        super(builder.key);
        name = ValidationUtils.requireNonNull(builder.name, "No name!");
        description = builder.description;
        type = ValidationUtils.requireNonNull(builder.type, "No type!");
        mode = builder.mode;
        if (builder.id >= 0) {
            setId(builder.id);
        }
        group = builder.group;
        priority = builder.priority;
    }

    public static Builder copyOf(@NotNull final Resource resource) {
        return new Builder(resource.getKey().getPublicKey(), resource.getService())
                .id(resource.getId())
                .name(resource.getName())
                .description(resource.getDescription())
                .type(resource.getType())
                .mode(resource.getMode())
                .group(resource.getGroup())
                .priority(resource.getPriority());
    }

    @NotNull
    public String getPublicKey() {
        return getKey().getPublicKey();
    }

    public @NotNull Service getService() {
        return getKey().service;
    }

    @NotNull
    public String getName() {
        return name;
    }

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

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

    @NotNull
    public DiQuotingMode getMode() {
        return mode;
    }

    @Nullable
    public ResourceGroup getGroup() {
        return group;
    }

    @Nullable
    public Integer getPriority() {
        return priority;
    }

    @NotNull
    public DiResource toView() {
        return DiResource.withKey(getKey().getPublicKey())
                .inService(getService().toView())
                .withName(getName())
                .withDescription(getDescription())
                .withType(getType())
                .inMode(getMode())
                .inGroup(group == null ? null : group.getKey().getPublicKey())
                .withPriority(priority)
                .build();
    }

    public static final class Key implements Comparable<Key> {
        @NotNull
        private final String key;
        private final @NotNull Service service;

        public Key(@NotNull final String key, final @NotNull Service service) {
            this.key = key;
            this.service = service;
        }

        @NotNull
        public String getPublicKey() {
            return key;
        }

        @NotNull
        public Service getService() {
            return service;
        }

        @Override
        public int compareTo(@NotNull final Key key) {
            return new CompareToBuilder()
                    .append(getPublicKey(), key.getPublicKey())
                    .append(getService(), key.getService())
                    .build();
        }

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

        @Override
        public int hashCode() {
            return 31 * key.hashCode() + service.hashCode();
        }

        @NotNull
        @Override
        public String toString() {
            return "Key{key='" + key + "', service.key=" + service.getKey() + "}";
        }
    }

    public static final class Builder {
        private long id = -1;
        @NotNull
        private final Key key;
        @Nullable
        private String name;
        @Nullable
        private String description;
        @Nullable
        private DiResourceType type;
        @NotNull
        private DiQuotingMode mode = DiQuotingMode.DEFAULT;
        @Nullable
        private ResourceGroup group;
        @Nullable
        private Integer priority;

        public Builder(@NotNull final String key, @NotNull final Service service) {
            this.key = new Key(key, service);
        }

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

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

        @NotNull
        public Builder type(final DiResourceType type) {
            this.type = type;
            return this;
        }

        @NotNull
        public Builder mode(@NotNull final DiQuotingMode mode) {
            this.mode = mode;
            return this;
        }

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

        @NotNull
        public Builder group(@Nullable final ResourceGroup resourceGroup) {
            this.group = resourceGroup;
            return this;
        }

        @NotNull
        public Builder group(@Nullable final String groupKey) {
            this.group =
                    groupKey == null ? null : Hierarchy.get().getResourceGroupReader().read(new ResourceGroup.Key(groupKey, key.getService()));
            return this;
        }

        @NotNull
        public Builder noGroup() {
            this.group = null;
            return this;
        }

        @NotNull
        public Builder priority(@Nullable final Integer priority) {
            this.priority = priority;
            return this;
        }

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