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

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
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.util.JsonSerializerBase;
import ru.yandex.qe.dispenser.api.util.ValidationUtils;


@JsonSerialize(using = DiListPageResponse.Serializer.class)
public class DiListPageResponse<T> extends DiListResponse<T> implements DiPage<T> {
    private final long totalResultsCount;
    private final long totalPagesCount;
    @Nullable
    private final URI nextPageUrl;
    @Nullable
    private final URI previousPageUrl;

    @JsonCreator
    public DiListPageResponse(@JsonProperty("result") @NotNull final Collection<T> results,
                              @JsonProperty("count") final long count,
                              @JsonProperty("totalPages") final long totalPages,
                              @JsonProperty("next") @Nullable final String next,
                              @JsonProperty("previous") @Nullable final String previous) throws URISyntaxException {
        super(results);
        this.totalResultsCount = count;
        this.totalPagesCount = totalPages;
        this.nextPageUrl = next == null ? null : new URI(next);
        this.previousPageUrl = previous == null ? null : new URI(previous);
    }

    private DiListPageResponse(final Builder<T> builder) {
        super(Collections.unmodifiableCollection(ValidationUtils.requireNonNull(builder.results, "Results are required")));
        this.totalResultsCount = ValidationUtils.requireNonNull(builder.totalResultsCount, "Total results count is required");
        this.totalPagesCount = ValidationUtils.requireNonNull(builder.totalPagesCount, "Total pages count is required");
        this.nextPageUrl = builder.nextPageUrl;
        this.previousPageUrl = builder.previousPageUrl;
    }

    public static <T> Builder<T> builder() {
        return new Builder<>();
    }

    public long getTotalResultsCount() {
        return totalResultsCount;
    }

    public long getTotalPagesCount() {
        return totalPagesCount;
    }

    @Nullable
    public URI getNextPageUrl() {
        return nextPageUrl;
    }

    @Nullable
    public URI getPreviousPageUrl() {
        return previousPageUrl;
    }

    @Override
    public boolean hasNext() {
        return nextPageUrl != null;
    }

    static class Serializer<R extends DiListPageResponse<?>> extends JsonSerializerBase<R> {
        @Override
        public void serialize(@NotNull final R response,
                              @NotNull final JsonGenerator jg,
                              @NotNull final SerializerProvider sp) throws IOException {
            jg.writeStartObject();
            jg.writeNumberField("count", response.getTotalResultsCount());
            jg.writeNumberField("totalPages", response.getTotalPagesCount());
            jg.writeStringField("next", Objects.toString(response.getNextPageUrl(), null));
            jg.writeStringField("previous", Objects.toString(response.getPreviousPageUrl(), null));
            jg.writeArrayFieldStart("result");
            for (final Object el : response) {
                jg.writeObject(el);
            }
            jg.writeEndArray();
            jg.writeEndObject();
        }
    }

    public static class Builder<T> implements DtoBuilder<DiListResponse<T>> {
        @Nullable
        private Collection<T> results;
        @Nullable
        private Long totalResultsCount;
        @Nullable
        private Long totalPagesCount;
        @Nullable
        private URI nextPageUrl;
        @Nullable
        private URI previousPageUrl;

        public Builder<T> withResults(final Collection<T> results) {
            this.results = results;
            return this;
        }

        public Builder<T> withTotalResultsCount(final Long totalResultsCount) {
            this.totalResultsCount = totalResultsCount;
            return this;
        }

        public Builder<T> withTotalPagesCount(final Long totalPagesCount) {
            this.totalPagesCount = totalPagesCount;
            return this;
        }

        public Builder<T> withNextPageUrl(final URI nextPageUrl) {
            this.nextPageUrl = nextPageUrl;
            return this;
        }

        public Builder<T> withPreviousPageUrl(final URI previousPageUrl) {
            this.previousPageUrl = previousPageUrl;
            return this;
        }

        @NotNull
        @Override
        public DiListPageResponse<T> build() {
            return new DiListPageResponse<>(this);
        }
    }
}
