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 java.util.Optional;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
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.SerializationUtils;
import ru.yandex.qe.dispenser.api.util.ValidationUtils;
import ru.yandex.qe.dispenser.api.v1.project.DiProjectFieldsHolder;

@JsonDeserialize(using = DiProject.Deserializer.class)
public final class DiProject extends KeyBase<String> implements DiProjectFieldsHolder {

    @NotNull
    private final String name;
    @NotNull
    private final String description;
    @Nullable
    private final Integer abcServiceId;
    @NotNull
    private final DiPersonGroup responsibles;
    @NotNull
    private final DiPersonGroup members;
    @Nullable
    private final String parentProjectKey;
    @NotNull
    private final List<String> subprojectKeys;
    @Nullable
    private final String person;

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

    @NotNull
    public static Builder copyOf(@NotNull final DiProject project) {
        return new Builder(project.getKey())
                .withName(project.getName())
                .withDescription(project.getDescription())
                .withAbcServiceId(project.getAbcServiceId())
                .withResponsibles(project.getResponsibles())
                .withMembers(project.getMembers())
                .withParentProject(project.getParentProjectKey())
                .withSubprojects(project.getSubprojectKeys())
                .makePersonal(project.getPerson());
    }

    private DiProject(@NotNull final BuilderBase<?> builder) {
        super(builder);
        this.name = ValidationUtils.requireNonNull(builder.name, "Name is required");
        this.description = builder.description;
        this.abcServiceId = builder.abcServiceId;
        this.responsibles = Optional.ofNullable(builder.responsibles).orElse(DiPersonGroup.EMPTY);
        this.members = Optional.ofNullable(builder.members).orElse(DiPersonGroup.EMPTY);
        this.parentProjectKey = builder.parentProjectKey;
        // TODO move sorting to utils?
        this.subprojectKeys = Collections.unmodifiableList(builder.subprojectKeys.stream().sorted().collect(Collectors.toList()));
        this.person = builder.person;
    }

    @Nullable
    public String getPerson() {
        return person;
    }

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

    @NotNull
    @Override
    public String getDescription() {
        return description;
    }

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

    @NotNull
    @Override
    public DiPersonGroup getResponsibles() {
        return responsibles;
    }

    @NotNull
    @Override
    public DiPersonGroup getMembers() {
        return members;
    }

    @Nullable
    @Override
    public String getParentProjectKey() {
        return parentProjectKey;
    }

    @NotNull
    @Override
    public Collection<String> getSubprojectKeys() {
        return subprojectKeys;
    }

    @JsonIgnore
    public boolean isRoot() {
        return getParentProjectKey() == null;
    }

    @JsonIgnore
    public boolean isLeaf() {
        return getSubprojectKeys().isEmpty();
    }

    @Override
    public String toString() {
        return "DiProject{key='" + getKey() + "'}";
    }

    public static abstract class BuilderBase<B extends BuilderBase<B>> extends KeyBase.Builder<String, B> {
        @Nullable
        protected String name;
        @NotNull
        protected String description = "";
        @Nullable
        protected Integer abcServiceId;
        @Nullable
        protected DiPersonGroup responsibles;
        @Nullable
        protected DiPersonGroup members;
        @Nullable
        protected String parentProjectKey;
        @NotNull
        protected List<String> subprojectKeys = new ArrayList<>();
        @Nullable
        protected String person;

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

        @NotNull
        public B makePersonal(@Nullable final String login) {
            //TODO FIX
            if ("null".equals(login)) {
                this.person = null;
            } else {
                this.person = login;
            }
            return self();
        }

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

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

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

        @NotNull
        public B withResponsibles(@NotNull final DiPersonGroup responsibles) {
            this.responsibles = responsibles;
            return self();
        }

        @NotNull
        public B withMembers(@NotNull final DiPersonGroup members) {
            this.members = members;
            return self();
        }

        @NotNull
        public B withParentProject(@Nullable final String parentProjectKey) {
            this.parentProjectKey = parentProjectKey;
            return self();
        }

        @NotNull
        public B withSubprojects(@NotNull final Collection<String> subprojectKeys) {
            this.subprojectKeys.addAll(subprojectKeys);
            return self();
        }

        @NotNull
        public B withSubprojects(@NotNull final String... subprojectKeys) {
            return withSubprojects(Arrays.asList(subprojectKeys));
        }

        protected @NotNull DiProject buildProject() {
            return new DiProject(this);
        }
    }

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

        @Override
        @NotNull
        public DiProject build() {
            return buildProject();
        }
    }

    static final class Deserializer extends JsonDeserializerBase<DiProject> {
        @NotNull
        @Override
        public DiProject deserialize(@NotNull final JsonParser jp,
                                     @NotNull final DeserializationContext dc) throws IOException {
            final JsonNode json = toJson(jp);
            return new Builder(json.get("key").asText())
                    .makePersonal(json.has("person") ? json.get("person").asText() : null)
                    .withName(json.get("name").asText())
                    .withDescription(json.get("description").asText())
                    .withAbcServiceId(json.hasNonNull("abcServiceId") ? json.get("abcServiceId").asInt() : null)
                    .withResponsibles(SerializationUtils.convertValue(json.get("responsibles"), DiPersonGroup.class))
                    .withMembers(SerializationUtils.convertValue(json.get("members"), DiPersonGroup.class))
                    .withParentProject(Optional.ofNullable(json.get("parentProjectKey")).filter(JsonNode::isTextual).map(JsonNode::asText).orElse(null))
                    .withSubprojects(json.hasNonNull("subprojectKeys") ?
                            SerializationUtils.convertValue(json.get("subprojectKeys"), String[].class) : new String[0])
                    .build();
        }
    }
}
