/*
 * 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.server.aggregate;

import com.google.protobuf.Timestamp;
import io.spine.base.Time;
import io.spine.base.Time.Provider;
import io.spine.core.BoundedContextNames;
import io.spine.core.Event;
import io.spine.server.aggregate.given.ReadOperationTestEnv.TestAggregate;
import io.spine.server.storage.StorageFactory;
import io.spine.server.storage.memory.InMemoryStorageFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import static com.google.common.truth.Truth.assertThat;
import static io.spine.server.aggregate.given.ReadOperationTestEnv.events;
import static io.spine.server.aggregate.given.ReadOperationTestEnv.snapshot;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

@DisplayName("ReadOperation should")
class ReadOperationTest {

    private static final String ID = "test-aggregate-ID";
    private static final StorageFactory storageFactory =
            InMemoryStorageFactory.newInstance(BoundedContextNames.assumingTests(), false);

    private AggregateStorage<String> storage;

    @BeforeAll
    static void prepare() {
        Time.Provider provider = new TickingTime();
        Time.setProvider(provider);
    }

    @AfterAll
    static void unPrepare() {
        Time.resetProvider();
    }

    @BeforeEach
    void setUp() {
        storage = storageFactory.createAggregateStorage(TestAggregate.class);
    }

    @Test
    @DisplayName("read all events if there are a few")
    void readAll() {
        int eventCount = 10;
        fillEvents(eventCount);
        AggregateReadRequest<String> request = new AggregateReadRequest<>(ID, 100);
        ReadOperation<String> operation = new ReadOperation<>(storage, request);
        Optional<AggregateStateRecord> record = operation.perform();
        assertTrue(record.isPresent());
        AggregateStateRecord stateRecord = record.get();

        assertFalse(stateRecord.hasSnapshot());
        List<Event> events = stateRecord.getEventList();
        assertThat(events).hasSize(eventCount);
    }

    @Test
    @DisplayName("read snapshot if present")
    void readSnapshot() {
        fillEventsWithSnapshot(5);
        AggregateReadRequest<String> request = new AggregateReadRequest<>(ID, 100);
        ReadOperation<String> operation = new ReadOperation<>(storage, request);
        Optional<AggregateStateRecord> record = operation.perform();
        assertTrue(record.isPresent());
        AggregateStateRecord stateRecord = record.get();

        assertTrue(stateRecord.hasSnapshot());
    }

    @Test
    @DisplayName("strip all events which occurred before snapshot")
    void trimBeforeSnapshot() {
        fillEvents(3);
        fillEventsWithSnapshot(5);
        int expectedEventCount = 7;
        fillEvents(expectedEventCount);

        AggregateReadRequest<String> request = new AggregateReadRequest<>(ID, 100);
        ReadOperation<String> operation = new ReadOperation<>(storage, request);
        Optional<AggregateStateRecord> record = operation.perform();
        assertTrue(record.isPresent());
        AggregateStateRecord stateRecord = record.get();

        assertTrue(stateRecord.hasSnapshot());
        assertThat(stateRecord.getEventList()).hasSize(expectedEventCount);
    }

    private void fillEvents(int count) {
        List<Event> events = events(count);
        storage.write(ID, AggregateStateRecord
                .newBuilder()
                .addAllEvent(events)
                .build());
    }

    private void fillEventsWithSnapshot(int count) {
        List<Event> events = events(count);
        Snapshot snapshot = snapshot();
        storage.write(ID, AggregateStateRecord
                .newBuilder()
                .addAllEvent(events)
                .setSnapshot(snapshot)
                .build());
    }

    public static final class TickingTime implements Provider {

        private Timestamp current;

        public TickingTime() {
            this.current = Time.getCurrentTime();
        }

        @Override
        public Timestamp getCurrentTime() {
            current = current.toBuilder()
                             .setSeconds(current.getSeconds() + 1)
                             .build();
            return current;
        }
    }
}

@@ -1046,109 +1046,8 @@
 e;%0A%0A
-import com.google.protobuf.Timestamp;%0Aimport io.spine.base.Time;%0Aimport io.spine.base.Time.Provider;%0A
 impo
@@ -1300,87 +1300,8 @@
 ry;%0A
-import org.junit.jupiter.api.AfterAll;%0Aimport org.junit.jupiter.api.BeforeAll;%0A
 impo
@@ -2117,227 +2117,8 @@
 e;%0A%0A
-    @BeforeAll%0A    static void prepare() %7B%0A        Time.Provider provider = new TickingTime();%0A        Time.setProvider(provider);%0A    %7D%0A%0A    @AfterAll%0A    static void unPrepare() %7B%0A        Time.resetProvider();%0A    %7D%0A%0A
     
@@ -4640,455 +4640,7 @@
    %7D
-%0A%0A    public static final class TickingTime implements Provider %7B%0A%0A        private Timestamp current;%0A%0A        public TickingTime() %7B%0A            this.current = Time.getCurrentTime();%0A        %7D%0A%0A        @Override%0A        public Timestamp getCurrentTime() %7B%0A            current = current.toBuilder()%0A                             .setSeconds(current.getSeconds() + 1)%0A                             .build();%0A            return current;%0A        %7D%0A    %7D
 %0A%7D%0A

Remove custom `Time.Provider`.