package ru.yandex.qe.dispenser.client.v1.impl;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.apache.cxf.jaxrs.client.WebClient;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.util.JsonSerializerBase;
import ru.yandex.qe.dispenser.api.v1.request.DiEntityReference;
import ru.yandex.qe.dispenser.api.v1.request.DiServiceEntityReference;
import ru.yandex.qe.dispenser.api.v1.response.DiEntityOwnershipResponse;
import ru.yandex.qe.dispenser.client.v1.builder.GetEntityOwnershipsRequestBuilder;

public final class GetEntityOwnershipsRequestBuilderImpl implements GetEntityOwnershipsRequestBuilder.InService.BySpecification {
    public static final int LIMIT = 1000;
    @NotNull
    private final Supplier<WebClient> clients;
    @NotNull
    private final RequestBody requestBody;

    @NotNull
    private final Map<String, Long> forHost = new HashMap<>();

    @Nullable
    private String serviceKey;
    @Nullable
    private String specificationKey;

    GetEntityOwnershipsRequestBuilderImpl(@NotNull final Supplier<WebClient> clients) {
        this.clients = clients;
        this.requestBody = new RequestBody();
    }

    @NotNull
    @Override
    public GetEntityOwnershipsRequestBuilder.InService.BySpecification ofProject(@NotNull final String projectKey) {
        requestBody.addProject(projectKey);
        return this;
    }

    @NotNull
    @Override
    public GetEntityOwnershipsRequestBuilder.InService.BySpecification forHost(@NotNull final String host, final long startTs) {
        this.forHost.put(host, startTs);
        return this;
    }

    @NotNull
    @Override
    public GetEntityOwnershipsRequestBuilder.InService.BySpecification inLeafs() {
        requestBody.setInLeafs();
        return this;
    }

    @NotNull
    @Override
    public GetEntityOwnershipsRequestBuilder.InService<?> inService(@NotNull final String serviceKey) {
        this.serviceKey = serviceKey;
        return this;
    }

    @NotNull
    @Override
    public GetEntityOwnershipsRequestBuilder.InService.BySpecification bySpecification(@NotNull final String specificationKey) {
        this.specificationKey = specificationKey;
        return this;
    }

    @NotNull
    @Override
    public GetEntityOwnershipsRequestBuilder.InService.BySpecification withEntityKey(@NotNull final String entityKey) {
        Objects.requireNonNull(serviceKey, "Execute inService before!");
        Objects.requireNonNull(specificationKey, "Execute bySpecification before!");
        requestBody.addEntity(DiServiceEntityReference.withKey(entityKey).bySpecification(specificationKey).inService(serviceKey).build());
        return this;
    }


    @NotNull
    @Override
    public DiEntityOwnershipResponse perform() {
        final WebClient client = clients.get().path("/entity-ownerships");
        if (forHost.size() == 0) {
            return client.post(requestBody, DiEntityOwnershipResponse.class);
        } else {
            if (!requestBody.getEntityReferences().isEmpty() || !requestBody.getProjectKyes().isEmpty()) {
                throw new RuntimeException("forHost option can't be used with entity or project params");
            }
            return client.path("/byhost").query("limit", LIMIT).post(forHost, DiEntityOwnershipResponse.class);
        }
    }

    @JsonSerialize(using = RequestBody.Serializer.class)
    private static class RequestBody {
        private final Collection<DiServiceEntityReference> entityReferences = new HashSet<>();
        private final Collection<String> projectKyes = new HashSet<>();
        private boolean inLeafs;

        void addEntity(@NotNull final DiServiceEntityReference entityReference) {
            entityReferences.add(entityReference);
        }

        void addProject(@NotNull final String projectKey) {
            projectKyes.add(projectKey);
        }

        void setInLeafs() {
            inLeafs = true;
        }

        public Collection<DiServiceEntityReference> getEntityReferences() {
            return entityReferences;
        }

        public Collection<String> getProjectKyes() {
            return projectKyes;
        }

        static class Serializer extends JsonSerializerBase<RequestBody> {
            @Override
            public void serialize(@NotNull final RequestBody value,
                                  @NotNull final JsonGenerator jg,
                                  @NotNull final SerializerProvider sp) throws IOException {
                jg.writeStartObject();
                jg.writeArrayFieldStart("entities");
                for (final DiEntityReference entityReference : value.entityReferences) {
                    jg.writeObject(entityReference);
                }
                jg.writeEndArray();
                jg.writeArrayFieldStart("projectKyes");
                for (final String projectKey : value.projectKyes) {
                    jg.writeString(projectKey);
                }
                jg.writeEndArray();
                if (value.inLeafs) {
                    jg.writeObjectField("inLeafs", true);
                }
                jg.writeEndObject();
            }
        }
    }
}
