/*
 * Copyright 2018, 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;

import com.google.common.annotations.VisibleForTesting;
import io.spine.core.Command;
import io.spine.core.CommandEnvelope;
import io.spine.core.TenantId;
import io.spine.server.event.Enricher;
import io.spine.server.tenant.TenantAwareRunner;
import io.spine.testing.client.TestActorRequestFactory;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkState;

/**
 * A black box bounded context for writing integration tests in a multitenant environment.
 */
@VisibleForTesting
public class MultitenantBlackBoxContext
        extends BlackBoxBoundedContext<MultitenantBlackBoxContext> {

    private TenantId tenantId;

    /**
     * Creates a new multi-tenant instance.
     */
    MultitenantBlackBoxContext(Enricher enricher) {
        super(true, enricher);
    }

    /**
     * Switches the bounded context to operate on behalf of the specified tenant.
     *
     * @param tenant new tenant ID
     * @return current instance
     */
    public MultitenantBlackBoxContext withTenant(TenantId tenant) {
        this.tenantId = tenant;
        return this;
    }

    @Override
    protected TestActorRequestFactory requestFactory() {
        return requestFactory(tenantId());
    }

    @Override
    protected EmittedCommands emittedCommands(CommandMemoizingTap commandTap) {
        List<Command> allCommands = commandTap.commands();
        List<Command> tenantCommands = allCommands.stream()
                                                  .filter(new IsTenantCommand(tenantId))
                                                  .collect(Collectors.toList());
        return new EmittedCommands(tenantCommands);
    }

    @Override
    protected EmittedEvents emittedEvents() {
        TenantAwareRunner tenantAwareRunner = TenantAwareRunner.with(tenantId);
        EmittedEvents events = tenantAwareRunner.evaluate(super::emittedEvents);
        return events;
    }

    private TenantId tenantId() {
        checkState(tenantId != null,
                   "Set a tenant ID before calling receive and assert methods");
        return tenantId;
    }

    /**
     * Creates a new {@link io.spine.client.ActorRequestFactory actor request factory} for tests
     * with a provided tenant ID.
     *
     * @param tenantId
     *         an identifier of a tenant that is executing requests in this Bounded Context
     * @return a new request factory instance
     */
    private static TestActorRequestFactory requestFactory(TenantId tenantId) {
        return TestActorRequestFactory.newInstance(MultitenantBlackBoxContext.class, tenantId);
    }

    private static class IsTenantCommand implements Predicate<Command> {

        private final TenantId tenantToMatch;

        private IsTenantCommand(TenantId tenantToMatch) {
            this.tenantToMatch = tenantToMatch;
        }

        @Override
        public boolean test(Command command) {
            TenantId commandTenant = CommandEnvelope.of(command)
                                                    .getTenantId();
            return commandTenant.equals(tenantToMatch);
        }
    }
}

@@ -1163,24 +1163,17 @@
 .Command
-Envelope
+s
 ;%0Aimport
@@ -2030,16 +2030,31 @@
 m tenant
+%0A     *        
  new ten
@@ -4074,81 +4074,9 @@
 mand
-Envelope.of(command)%0A                                                    
+s
 .get
@@ -4084,16 +4084,23 @@
 enantId(
+command
 );%0A     

Use `Commands.getTenantId`