/*
 * Copyright 2019, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.testing.server.blackbox.verify.query;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
import com.google.common.truth.extensions.proto.IterableOfProtosSubject;
import com.google.common.truth.extensions.proto.ProtoSubject;
import com.google.protobuf.Message;
import io.spine.client.QueryResponse;
import io.spine.core.Status;
import io.spine.core.Status.StatusCase;
import io.spine.core.Version;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.protos;
import static java.util.stream.Collectors.toList;

/**
 * assertQueryResult(query)
 *      .ignoringFieldAbsence()
 *      .containsExactly(state1, state2)
 *      or
 *      .containsElementsIn(ImmutableList.of(state1, state2, state3))
 *      or
 *      .containsSingleEntityStateThat()
 *      or
 *      .containsSingleEntityVersionThat()
 *      or
 *      .containsEntityVersionsSuchThat()
 *      .ignoringFieldAbsence()
 *      .contains(version)
 *      or
 *      .hasStatus(Status)
 */

@VisibleForTesting
public final class QueryResultSubject
        extends IterableOfProtosSubject<QueryResultSubject, Message, Iterable<Message>> {

    private final Collection<Version> entityVersions = new ArrayList<>();
    private Status status;

    private QueryResultSubject(FailureMetadata failureMetadata,
                               Iterable<Message> entityStates) {
        super(failureMetadata, entityStates);
    }

    private void setResponseData(QueryResponse queryResponse) {
        entityVersions.addAll(extractEntityVersions(queryResponse));
        status = extractStatus(queryResponse);
    }

    public static
    QueryResultSubject assertQueryResult(@Nullable QueryResponse queryResponse) {
        Iterable<Message> entityStates = extractEntityStates(queryResponse);
        QueryResultSubject subject = assertAbout(queryResult()).that(entityStates);
        if (queryResponse == null) {
            subject.failWithoutActual(simpleFact("`QueryResult` should never be `null`"));
            return subject;
        }
        subject.setResponseData(queryResponse);
        return subject;
    }

    private static Iterable<Message> extractEntityStates(@Nullable QueryResponse queryResponse) {
        if (queryResponse == null) {
            return ImmutableList.of();
        }
        List<Message> result = queryResponse.states()
                                            .stream()
                                            .map(Message.class::cast)
                                            .collect(toList());
        return result;
    }

    private static Collection<Version> extractEntityVersions(QueryResponse queryResponse) {
        return ImmutableList.of();
    }

    private static Status extractStatus(QueryResponse queryResponse) {
        return queryResponse.getResponse()
                            .getStatus();
    }

    public void hasStatus(StatusCase statusCase) {
        assertThat(status.getStatusCase()).isEqualTo(statusCase);
    }

    public ProtoSubject<?, Message> hasStatusThat() {
        return check("getResponse().getStatus()").about(protos())
                                                 .that(status);
    }

    static
    Subject.Factory<QueryResultSubject, Iterable<Message>> queryResult() {
        return QueryResultSubject::new;
    }
}

@@ -1702,16 +1702,53 @@
 il.List;
+%0Aimport java.util.function.Predicate;
 %0A%0Aimport
@@ -2032,16 +2032,75 @@
 .toList;
+%0Aimport static org.junit.jupiter.api.Assertions.assertTrue;
 %0A%0A/**%0A *
@@ -4410,32 +4410,390 @@
 tatus();%0A    %7D%0A%0A
+    @SuppressWarnings(%22unchecked%22) // It's up to user to provide the matching predicate%0A    public %3CM extends Message%3E void containsAllMatching(Predicate%3CM%3E predicate) %7B%0A        Iterable%3CMessage%3E entityStates = actual();%0A        entityStates.forEach(state -%3E %7B%0A            M cast = (M) state;%0A            assertTrue(predicate.test(cast));%0A        %7D);%0A    %7D%0A%0A
     public void 

Add a convenience method for assessing received entity state