package ru.yandex.qe.dispenser.api.v1;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.DtoBuilder;
import ru.yandex.qe.dispenser.api.KeyBase;
import ru.yandex.qe.dispenser.api.util.JsonDeserializerBase;
import ru.yandex.qe.dispenser.api.util.JsonSerializerBase;
import ru.yandex.qe.dispenser.api.util.SerializationUtils;
import ru.yandex.qe.dispenser.api.util.ValidationUtils;

@JsonSerialize(using = DiService.Serializer.class)
@JsonDeserialize(using = DiService.Deserializer.class)
public final class DiService extends KeyBase<String> {
    @NotNull
    private final String name;
    @Nullable
    private final Integer abcServiceId;
    @NotNull
    private final List<String> admins;
    @NotNull
    private final List<String> trustees;
    @Nullable
    private final Integer priority;
    @Nullable
    private final Settings settings;

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

    private DiService(@NotNull final BuilderBase<?> builder) {
        super(builder);
        this.name = ValidationUtils.requireNonNull(builder.name, "Name is required");
        this.abcServiceId = builder.abcServiceId;
        this.admins = Collections.unmodifiableList(builder.admins);
        this.trustees = Collections.unmodifiableList(builder.trustees);
        this.priority = builder.priority;
        this.settings = builder.settings;
    }

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

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

    @NotNull
    public List<String> getAdmins() {
        return admins;
    }

    @NotNull
    public List<String> getTrustees() {
        return trustees;
    }

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

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

    @JsonSerialize(using = DiService.SettingsSerializer.class)
    public static final class Settings {
        public static final Settings DEFAULT = new Builder().build();

        private final boolean accountActualValuesInQuotaDistribution;
        private final boolean requireZeroQuotaUsageForProjectDeletion;
        private final boolean usesProjectHierarchy;
        private final boolean manualQuotaAllocation;

        @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;
        }

        public boolean accountActualValuesInQuotaDistribution() {
            return accountActualValuesInQuotaDistribution;
        }

        public boolean requireZeroQuotaUsageForProjectDeletion() {
            return requireZeroQuotaUsageForProjectDeletion;
        }

        public boolean isManualQuotaAllocation() {
            return manualQuotaAllocation;
        }

        public boolean usesProjectHierarchy() {
            return usesProjectHierarchy;
        }

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

            public static DiService.Settings.Builder builder() {
                return new DiService.Settings.Builder();
            }

            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;
            }

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

    public static abstract class BuilderBase<B extends BuilderBase<B>> extends KeyBase.Builder<String, B> {
        @Nullable
        protected String name;
        @Nullable
        private Integer abcServiceId;
        @NotNull
        protected List<String> admins = new ArrayList<>();
        @NotNull
        protected List<String> trustees = new ArrayList<>();
        @Nullable
        public Integer priority;
        @Nullable
        public Settings settings;

        protected BuilderBase(@NotNull final String key) {
            super(key);
        }

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

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

        @NotNull
        public B withAdmins(@NotNull final Collection<String> logins) {
            this.admins.addAll(logins);
            return self();
        }

        @NotNull
        public B withAdmins(@NotNull final String... logins) {
            return withAdmins(Arrays.asList(logins));
        }

        @NotNull
        public B withTrustees(@NotNull final Collection<String> logins) {
            this.trustees.addAll(logins);
            return self();
        }

        @NotNull
        public B withTrustees(@NotNull final String... logins) {
            return withTrustees(Arrays.asList(logins));
        }

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

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

        protected @NotNull DiService buildService() {
            return new DiService(this);
        }
    }

    public static final class Builder extends BuilderBase<Builder> implements DtoBuilder<DiService> {
        private Builder(@NotNull final String key) {
            super(key);
        }

        @NotNull
        @Override
        public DiService build() {
            return buildService();
        }
    }

    static final class Serializer extends JsonSerializerBase<DiService> {
        @Override
        public void serialize(final @NotNull DiService service, final @NotNull JsonGenerator jg,
                              final @NotNull SerializerProvider sp) throws IOException {
            jg.writeStartObject();
            jg.writeStringField("key", service.getKey());
            jg.writeStringField("name", service.getName());
            jg.writeObjectField("abcServiceId", service.getAbcServiceId());
            jg.writeObjectField("admins", service.getAdmins());
            jg.writeObjectField("trustees", service.getTrustees());
            jg.writeObjectField("priority", service.getPriority());
            if (service.getSettings() != null) {
                jg.writeObjectField("settings", service.getSettings());
            }
            jg.writeEndObject();
        }
    }

    static final class Deserializer extends JsonDeserializerBase<DiService> {
        @NotNull
        @Override
        public DiService deserialize(@NotNull final JsonParser jp,
                                     @NotNull final DeserializationContext dc) throws IOException {
            final JsonNode json = jp.getCodec().readValue(jp, JsonNode.class);
            return withKey(json.get("key").asText())
                    .withName(json.get("name").asText())
                    .withAbcServiceId(json.hasNonNull("abcServiceId") ? json.get("abcServiceId").asInt() : null)
                    .withAdmins(SerializationUtils.convertValue(json.get("admins"), String[].class))
                    .withTrustees(SerializationUtils.convertValue(json.get("trustees"), String[].class))
                    .withPriority(json.hasNonNull("priority") ? json.get("priority").asInt() : null)
                    .withSettings(json.hasNonNull("settings") ? parseSettings(json.get("settings")) : null)
                    .build();
        }

        @NotNull
        private DiService.Settings parseSettings(@NotNull final JsonNode json) {
            final DiService.Settings.Builder builder = new DiService.Settings.Builder();

            final JsonNode accountActualValues = json.get("accountActualValuesInQuotaDistribution");
            if (accountActualValues != null) {
                builder.accountActualValuesInQuotaDistribution(accountActualValues.asBoolean());
            }

            final JsonNode requireZeroQuotaUsage = json.get("requireZeroQuotaUsageForProjectDeletion");
            if (requireZeroQuotaUsage != null) {
                builder.requireZeroQuotaUsageForProjectDeletion(requireZeroQuotaUsage.asBoolean());
            }

            final JsonNode usesProjectHierarchy = json.get("usesProjectHierarchy");
            if (usesProjectHierarchy != null) {
                builder.usesProjectHierarchy(usesProjectHierarchy.asBoolean());
            }

            final JsonNode manualQuotaAllocation = json.get("manualQuotaAllocation");
            if (manualQuotaAllocation != null) {
                builder.manualQuotaAllocation(manualQuotaAllocation.asBoolean());
            }

            return builder.build();
        }
    }

    static final class SettingsSerializer extends JsonSerializerBase<Settings> {
        @Override
        public void serialize(final @NotNull Settings settings, final @NotNull JsonGenerator jg,
                              final @NotNull SerializerProvider sp) throws IOException {
            jg.writeStartObject();
            jg.writeObjectField("accountActualValuesInQuotaDistribution", settings.accountActualValuesInQuotaDistribution());
            jg.writeObjectField("requireZeroQuotaUsageForProjectDeletion", settings.requireZeroQuotaUsageForProjectDeletion());
            jg.writeObjectField("usesProjectHierarchy", settings.usesProjectHierarchy());
            jg.writeObjectField("manualQuotaAllocation", settings.isManualQuotaAllocation());
            jg.writeEndObject();
        }
    }
}
