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

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.fasterxml.jackson.annotation.JsonValue;
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 ru.yandex.qe.dispenser.api.DtoBuilder;
import ru.yandex.qe.dispenser.api.util.JsonDeserializerBase;
import ru.yandex.qe.dispenser.api.util.SerializationUtils;

@JsonDeserialize(using = DiPersonInfo.Deserializer.class)
public class DiPersonInfo {
    @NotNull
    private final String login;
    @NotNull
    private final Map<ProjectRole, Set<String>> projectKeysByRole;
    @NotNull
    private final Set<String> adminedServiceKeys;
    private final boolean isDispenserAdmin;

    @NotNull
    public static Builder forPerson(@NotNull final String login) {
        return new Builder(login);
    }

    private DiPersonInfo(@NotNull final Builder builder) {
        this.login = builder.login;
        this.projectKeysByRole = Collections.unmodifiableMap(builder.projectKeysByRole);
        this.adminedServiceKeys = Collections.unmodifiableSet(builder.adminedServiceKeys);
        this.isDispenserAdmin = builder.isDispenserAdmin;
    }

    public String getLogin() {
        return login;
    }

    @NotNull
    public Map<ProjectRole, Set<String>> getProjectKeysByRole() {
        return projectKeysByRole;
    }

    @NotNull
    public Set<String> getAdminedServiceKeys() {
        return adminedServiceKeys;
    }

    public boolean isDispenserAdmin() {
        return isDispenserAdmin;
    }

    private boolean hasRoleInProjects(@NotNull final DiPersonInfo.ProjectRole role, @NotNull final String... projectKeys) {
        return projectKeysByRole.getOrDefault(role, Collections.emptySet()).containsAll(Arrays.asList(projectKeys));
    }

    public boolean isMemberOfProjects(@NotNull final String... projectKeys) {
        return hasRoleInProjects(ProjectRole.MEMBER, projectKeys);
    }

    public boolean isResponsibleForProjects(@NotNull final String... projectKeys) {
        return hasRoleInProjects(ProjectRole.RESPONSIBLE, projectKeys);
    }

    public boolean isAdminOfServices(@NotNull final String... serviceKeys) {
        return adminedServiceKeys.containsAll(Arrays.asList(serviceKeys));
    }

    // Can be made outer later if needed and be used in other classes
    public enum ProjectRole {
        MEMBER("member"),
        RESPONSIBLE("responsible");

        private final String stringValue;

        ProjectRole(final String stringValue) {
            this.stringValue = stringValue;
        }

        @JsonValue
        @Override
        public String toString() {
            return stringValue;
        }
    }

    public static class Builder implements DtoBuilder<DiPersonInfo> {
        @NotNull
        private final String login;
        @NotNull
        private final Map<ProjectRole, Set<String>> projectKeysByRole = new EnumMap<>(ProjectRole.class);
        @NotNull
        private final Set<String> adminedServiceKeys = new TreeSet<>();
        private boolean isDispenserAdmin;

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

        private Builder hasRoleInProjects(@NotNull final DiPersonInfo.ProjectRole role, @NotNull final Collection<String> projectKeys) {
            projectKeysByRole.computeIfAbsent(role, r -> new TreeSet<>()).addAll(projectKeys);
            return this;
        }

        public Builder memberOfProjects(@NotNull final Collection<String> projectKeys) {
            return hasRoleInProjects(ProjectRole.MEMBER, projectKeys);
        }

        public Builder responsibleForProjects(@NotNull final Collection<String> projectKeys) {
            return hasRoleInProjects(ProjectRole.RESPONSIBLE, projectKeys);
        }

        public Builder adminOfServices(@NotNull final Collection<String> serviceKeys) {
            adminedServiceKeys.addAll(serviceKeys);
            return this;
        }

        public Builder setDispenserAdmin(final boolean dispenserAdmin) {
            isDispenserAdmin = dispenserAdmin;
            return this;
        }

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

    static final class Deserializer extends JsonDeserializerBase<DiPersonInfo> {
        @NotNull
        @Override
        public DiPersonInfo deserialize(final JsonParser jp, final DeserializationContext dc) throws IOException {
            final JsonNode json = toJson(jp);
            final Builder builder = new Builder(json.get("login").asText())
                    .adminOfServices(SerializationUtils.toStringSet(json.get("adminedServiceKeys")))
                    .setDispenserAdmin(json.get("dispenserAdmin").asBoolean());
            final JsonNode projectKeysByRoleNode = json.get("projectKeysByRole");
            if (projectKeysByRoleNode == null || projectKeysByRoleNode.isNull()) {
                throw new IllegalArgumentException("No value for 'projectKeysByRole' field!");
            }
            projectKeysByRoleNode.fieldNames().forEachRemaining(role -> {
                builder.hasRoleInProjects(
                        SerializationUtils.convertValue(role, ProjectRole.class),
                        SerializationUtils.toStringSet(projectKeysByRoleNode.get(role))
                );
            });
            return builder.build();
        }
    }
}
