package ru.yandex.qe.dispenser.domain;

import java.util.Optional;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.DtoBuilder;
import ru.yandex.qe.dispenser.api.v1.DiService;
import ru.yandex.qe.dispenser.domain.dao.service.ServiceReader;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.index.NormalizedPrimaryKeyBase;
import ru.yandex.qe.dispenser.domain.util.CollectionUtils;
import ru.yandex.qe.dispenser.domain.util.ValidationUtils;

/**
 * Провайдер {@link Resource ресурсов}. Провайдер предоставляет ресурсы, которые квотируются на определенном
 * {@link Project сервисе/проекте}.
 */
public final class Service extends NormalizedPrimaryKeyBase<String> {
    @NotNull
    private final String name;
    @Nullable
    private final Integer abcServiceId;
    @NotNull
    private final Settings settings;
    @Nullable
    private final Integer priority;

    private Service(@NotNull final Builder builder) {
        super(builder.key);
        this.name = ValidationUtils.requireNonNull(builder.name, "Service name is required");
        this.abcServiceId = builder.abcServiceId;
        this.settings = Optional.ofNullable(builder.settings).orElse(Settings.DEFAULT);
        this.priority = builder.priority;
        if (builder.id >= 0) {
            setId(builder.id);
        }
    }

    public static Builder withKey(@NotNull final String key) {
        return new Builder(key);
    }

    public static Builder copyOf(@NotNull final Service service) {
        return new Builder(service.getKey())
                .withId(service.getId())
                .withName(service.getName())
                .withAbcServiceId(service.getAbcServiceId())
                .withPriority(service.getPriority())
                .withSettings(service.getSettings());
    }

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

    @Nullable
    public Integer getAbcServiceId() {
        return abcServiceId;
    }

    @NotNull
    public Settings getSettings() {
        return settings;
    }

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


    @NotNull
    public DiService toView() {
        return toView(Hierarchy.get().getServiceReader(), false);
    }

    @NotNull
    public DiService toView(@NotNull final ServiceReader serviceReader) {
        return toView(serviceReader, false);
    }

    @NotNull
    public DiService toVerboseView() {
        return toView(Hierarchy.get().getServiceReader(), true);
    }

    /**
     * Conversion to DiService requires reading list of service admins.
     * This method allows to pass ServiceReader, with which this should be done (for example to read directly from DB).
     * Method with no param will read admins from cache.
     */
    @NotNull
    public DiService toView(@NotNull final ServiceReader serviceReader, final boolean verbose) {
        final DiService.Builder builder = DiService.withKey(getKey())
                .withName(getName())
                .withAbcServiceId(getAbcServiceId())
                .withAdmins(CollectionUtils.map(serviceReader.getAdmins(this), Person::getLogin))
                .withTrustees(CollectionUtils.map(serviceReader.getTrustees(this), Person::getLogin))
                .withPriority(priority);
        if (verbose && getSettings() != null) {
            builder.withSettings(DiService.Settings.builder()
                    .accountActualValuesInQuotaDistribution(getSettings().accountActualValuesInQuotaDistribution())
                    .requireZeroQuotaUsageForProjectDeletion(getSettings().requireZeroQuotaUsageForProjectDeletion())
                    .usesProjectHierarchy(getSettings().usesProjectHierarchy())
                    .manualQuotaAllocation(getSettings().isManualQuotaAllocation())
                    .build());
        }
        return builder.build();
    }

    public static final class Builder implements DtoBuilder<Service> {
        private long id = -1;
        @NotNull
        private final String key;
        @Nullable
        private String name;
        @Nullable
        private Integer abcServiceId;
        @Nullable
        private Settings settings;
        @Nullable
        private Integer priority;

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

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

        public Builder withAbcServiceId(@Nullable final Integer abcServiceId) {
            this.abcServiceId = abcServiceId;
            return this;
        }

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

        @NotNull
        public Builder withSettings(@NotNull final Settings settings) {
            this.settings = settings;
            return this;
        }

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

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

    public static final class Settings {
        private static final Settings DEFAULT = new Builder().build();

        private final boolean accountActualValuesInQuotaDistribution;
        private final boolean requireZeroQuotaUsageForProjectDeletion;
        private final boolean usesProjectHierarchy;
        private final boolean manualQuotaAllocation;
        @Nullable
        private final String resourcesMappingBeanName;

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

        private Settings(final Builder builder) {
            this.accountActualValuesInQuotaDistribution = builder.accountActualValuesInQuotaDistribution;
            this.requireZeroQuotaUsageForProjectDeletion = builder.requireZeroQuotaUsageForProjectDeletion;
            this.usesProjectHierarchy = builder.usesProjectHierarchy;
            this.manualQuotaAllocation = builder.manualQuotaAllocation;
            this.resourcesMappingBeanName = builder.resourcesMappingBeanName;
        }

        public boolean accountActualValuesInQuotaDistribution() {
            return accountActualValuesInQuotaDistribution;
        }

        public boolean requireZeroQuotaUsageForProjectDeletion() {
            return requireZeroQuotaUsageForProjectDeletion;
        }

        public boolean usesProjectHierarchy() {
            return usesProjectHierarchy;
        }

        public boolean isManualQuotaAllocation() {
            return manualQuotaAllocation;
        }

        @Nullable
        public String getResourcesMappingBeanName() {
            return resourcesMappingBeanName;
        }

        public Service.Settings.Builder copyBuilder() {
            return builder()
                    .accountActualValuesInQuotaDistribution(accountActualValuesInQuotaDistribution)
                    .requireZeroQuotaUsageForProjectDeletion(requireZeroQuotaUsageForProjectDeletion)
                    .usesProjectHierarchy(usesProjectHierarchy)
                    .manualQuotaAllocation(manualQuotaAllocation)
                    .resourcesMappingBeanName(resourcesMappingBeanName);
        }

        public static final class Builder implements DtoBuilder<Settings> {
            private boolean accountActualValuesInQuotaDistribution;
            private boolean requireZeroQuotaUsageForProjectDeletion;
            private boolean usesProjectHierarchy = true;
            private boolean manualQuotaAllocation;
            @Nullable
            private String resourcesMappingBeanName;

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

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

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

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

            public Builder resourcesMappingBeanName(@Nullable final String beanName) {
                this.resourcesMappingBeanName = beanName;
                return this;
            }

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