/*
 * Copyright 2022, TeamDev. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.server;

import com.google.common.collect.Sets;
import io.spine.base.Identifier;
import io.spine.core.Ack;
import io.spine.core.Command;
import io.spine.core.CommandValidationError;
import io.spine.grpc.MemoizingObserver;
import io.spine.server.given.transport.TestGrpcServer;
import io.spine.test.commandservice.CmdServDontHandle;
import io.spine.testing.TestValues;
import io.spine.testing.client.TestActorRequestFactory;
import io.spine.testing.logging.mute.MuteLogging;
import io.spine.testing.server.model.ModelTests;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Set;

import static io.spine.core.Status.StatusCase.ERROR;
import static io.spine.grpc.StreamObservers.memoizingObserver;
import static io.spine.protobuf.Messages.isNotDefault;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@DisplayName("`CommandService` should")
class CommandServiceTest {

    private CommandService service;

    private final Set<BoundedContext> boundedContexts = Sets.newHashSet();
    private BoundedContext projectsContext;

    private BoundedContext customersContext;
    private final MemoizingObserver<Ack> responseObserver = memoizingObserver();

    @BeforeEach
    void setUp() {
        ModelTests.dropAllModels();
        // Create Projects Bounded Context with one repository.
        projectsContext = BoundedContext.multitenant("Projects").build();
        var projectRepo = new Given.ProjectAggregateRepository();
        projectsContext.register(projectRepo);
        boundedContexts.add(projectsContext);

        // Create Customers Bounded Context with one repository.
        customersContext = BoundedContext.multitenant("Customers").build();
        var customerRepo = new Given.CustomerAggregateRepository();
        customersContext.register(customerRepo);
        boundedContexts.add(customersContext);

        // Expose two Bounded Contexts via an instance of {@code CommandService}.
        var builder = CommandService.newBuilder();
        for (var context : boundedContexts) {
            builder.add(context);
        }
        service = builder.build();
    }

    @AfterEach
    void tearDown() throws Exception {
        for (var boundedContext : boundedContexts) {
            boundedContext.close();
        }
    }

    @Test
    @DisplayName("post commands to appropriate bounded context")
    void postCommandsToBc() {
        verifyPostsCommand(Given.ACommand.createProject());
        verifyPostsCommand(Given.ACommand.createCustomer());
    }

    @Test
    @DisplayName("never retrieve removed bounded contexts from builder")
    void notRetrieveRemovedBc() {
        var builder = CommandService.newBuilder()
                .add(projectsContext)
                .add(customersContext)
                .remove(projectsContext);

        // Create BoundedContext map.
        var service = builder.build();
        assertNotNull(service);

        assertTrue(builder.contains(customersContext));
        assertFalse(builder.contains(projectsContext));
    }

    private void verifyPostsCommand(Command cmd) {
        MemoizingObserver<Ack> observer = memoizingObserver();
        service.post(cmd, observer);

        assertNull(observer.getError());
        assertTrue(observer.isCompleted());
        var acked = observer.firstResponse();
        var messageId = acked.getMessageId();
        assertEquals(cmd.getId(), Identifier.unpack(messageId));
    }

    @Test
    @DisplayName("return error status if command is unsupported")
    @MuteLogging
    void returnCommandUnsupportedError() {
        var factory = new TestActorRequestFactory(getClass());

        var unsupportedCmd = factory.createCommand(CmdServDontHandle.getDefaultInstance());

        service.post(unsupportedCmd, responseObserver);

        assertTrue(responseObserver.isCompleted());
        var result = responseObserver.firstResponse();
        assertNotNull(result);
        assertTrue(isNotDefault(result));
        var status = result.getStatus();
        assertEquals(ERROR, status.getStatusCase());
        var error = status.getError();
        assertEquals(CommandValidationError.getDescriptor().getFullName(), error.getType());
    }

    @Test
    @DisplayName("deploy to gRPC container")
    void deployToGrpcContainer() throws IOException {
        var grpcContainer = GrpcContainer.inProcess(TestValues.randomString())
                                         .addService(service)
                                         .build();
        grpcContainer.injectServer(new TestGrpcServer());
        assertTrue(grpcContainer.isScheduledForDeployment(service));

        grpcContainer.start();
        assertTrue(grpcContainer.isLive(service));

        grpcContainer.shutdown();
        assertFalse(grpcContainer.isLive(service));
    }
}

@@ -1982,24 +1982,80 @@
 .util.Set;%0A%0A
+import static com.google.common.truth.Truth.assertThat;%0A
 import stati
@@ -5063,28 +5063,13 @@
 sert
-Equals(cmd.getId(), 
+That(
 Iden
@@ -5093,16 +5093,56 @@
 sageId))
+%0A                .isEqualTo(cmd.getId())
 ;%0A    %7D%0A
@@ -5204,18 +5204,20 @@
 mand is 
-u
 n
+ot 
 supporte
@@ -5733,22 +5733,13 @@
 sert
-Equals(ERROR, 
+That(
 stat
@@ -5753,24 +5753,58 @@
 tatusCase())
+%0A                .isEqualTo(ERROR)
 ;%0A        va
@@ -5846,22 +5846,64 @@
   assert
+That(error.getType())%0A                .is
 Equal
-s
+To
 (Command
@@ -5951,25 +5951,8 @@
 me()
-, error.getType()
 );%0A 

Migrate equality to Truth