/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.accumulo.server.replication;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.IteratorSetting.Column;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.iterators.Combiner;
import org.apache.accumulo.core.iterators.DevNull;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
import org.apache.accumulo.core.replication.ReplicationSchema.StatusSection;
import org.apache.accumulo.server.replication.proto.Replication.Status;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

@Deprecated
public class StatusCombinerTest {

  private StatusCombiner combiner;
  private Key key;
  private Status.Builder builder;

  @BeforeEach
  public void initCombiner() throws IOException {
    key = new Key();
    combiner = new StatusCombiner();
    builder = Status.newBuilder();
    IteratorSetting cfg = new IteratorSetting(50, StatusCombiner.class);
    Combiner.setColumns(cfg, Collections.singletonList(new Column(StatusSection.NAME)));
    combiner.init(new DevNull(), cfg.getOptions(),
        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
  }

  @Test
  public void returnsSameObject() {
    Status status = StatusUtil.ingestedUntil(10);
    // When combining only one message, we should get back the same instance
    Status ret = combiner.typedReduce(key, Collections.singleton(status).iterator());
    assertEquals(status, ret);
    assertSame(status, ret);
  }

  @Test
  public void newStatusWithNewIngest() {
    Status orig = StatusUtil.fileCreated(100);
    Status status = StatusUtil.replicatedAndIngested(10, 20);
    Status ret = combiner.typedReduce(key, Arrays.asList(orig, status).iterator());
    assertEquals(10L, ret.getBegin());
    assertEquals(20L, ret.getEnd());
    assertEquals(100L, ret.getCreatedTime());
    assertFalse(ret.getClosed());
  }

  @Test
  public void newStatusWithNewIngestSingleBuilder() {
    Status orig = StatusUtil.fileCreated(100);
    Status status = StatusUtil.replicatedAndIngested(builder, 10, 20);
    Status ret = combiner.typedReduce(key, Arrays.asList(orig, status).iterator());
    assertEquals(10L, ret.getBegin());
    assertEquals(20L, ret.getEnd());
    assertEquals(100L, ret.getCreatedTime());
    assertFalse(ret.getClosed());
  }

  @Test
  public void commutativeNewFile() {
    Status newFile = StatusUtil.fileCreated(100), firstSync = StatusUtil.ingestedUntil(100),
        secondSync = StatusUtil.ingestedUntil(200);

    Status order1 =
        combiner.typedReduce(key, Arrays.asList(newFile, firstSync, secondSync).iterator()),
        order2 =
            combiner.typedReduce(key, Arrays.asList(secondSync, firstSync, newFile).iterator());

    assertEquals(order1, order2);
  }

  @Test
  public void commutativeNewFileSingleBuilder() {
    Status newFile = StatusUtil.fileCreated(100),
        firstSync = StatusUtil.ingestedUntil(builder, 100),
        secondSync = StatusUtil.ingestedUntil(builder, 200);

    Status order1 =
        combiner.typedReduce(key, Arrays.asList(newFile, firstSync, secondSync).iterator()),
        order2 =
            combiner.typedReduce(key, Arrays.asList(secondSync, firstSync, newFile).iterator());

    assertEquals(order1, order2);
  }

  @Test
  public void commutativeNewUpdates() {
    Status newFile = StatusUtil.fileCreated(100), firstSync = StatusUtil.ingestedUntil(100),
        secondSync = StatusUtil.ingestedUntil(200);

    Status order1 =
        combiner.typedReduce(key, Arrays.asList(newFile, firstSync, secondSync).iterator()),
        order2 =
            combiner.typedReduce(key, Arrays.asList(newFile, secondSync, firstSync).iterator());

    assertEquals(order1, order2);
  }

  @Test
  public void commutativeNewUpdatesSingleBuilder() {
    Status newFile = StatusUtil.fileCreated(100),
        firstSync = StatusUtil.ingestedUntil(builder, 100),
        secondSync = StatusUtil.ingestedUntil(builder, 200);

    Status order1 =
        combiner.typedReduce(key, Arrays.asList(newFile, firstSync, secondSync).iterator()),
        order2 =
            combiner.typedReduce(key, Arrays.asList(newFile, secondSync, firstSync).iterator());

    assertEquals(order1, order2);
  }

  @Test
  public void commutativeWithClose() {
    Status newFile = StatusUtil.fileCreated(100), closed = StatusUtil.fileClosed(),
        secondSync = StatusUtil.ingestedUntil(200);

    Status order1 =
        combiner.typedReduce(key, Arrays.asList(newFile, closed, secondSync).iterator()),
        order2 = combiner.typedReduce(key, Arrays.asList(newFile, secondSync, closed).iterator());

    assertEquals(order1, order2);
  }

  @Test
  public void commutativeWithCloseSingleBuilder() {
    Status newFile = StatusUtil.fileCreated(100), closed = StatusUtil.fileClosed(),
        secondSync = StatusUtil.ingestedUntil(builder, 200);

    Status order1 =
        combiner.typedReduce(key, Arrays.asList(newFile, closed, secondSync).iterator()),
        order2 = combiner.typedReduce(key, Arrays.asList(newFile, secondSync, closed).iterator());

    assertEquals(order1, order2);
  }

  @Test
  public void commutativeWithMultipleUpdates() {
    Status newFile = StatusUtil.fileCreated(100), update1 = StatusUtil.ingestedUntil(100),
        update2 = StatusUtil.ingestedUntil(200), repl1 = StatusUtil.replicated(50),
        repl2 = StatusUtil.replicated(150);

    Status order1 = combiner.typedReduce(key,
        Arrays.asList(newFile, update1, repl1, update2, repl2).iterator());

    // Got all replication updates before ingest updates
    Status permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, repl1, update1, repl2, update2).iterator());

    assertEquals(order1, permutation);

    // All replications before updates
    permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, repl1, repl2, update1, update2).iterator());

    assertEquals(order1, permutation);

    // All updates before replications
    permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, update1, update2, repl1, repl2, update1, update2).iterator());

    assertEquals(order1, permutation);
  }

  @Test
  public void commutativeWithMultipleUpdatesSingleBuilder() {
    Status newFile = StatusUtil.fileCreated(100), update1 = StatusUtil.ingestedUntil(builder, 100),
        update2 = StatusUtil.ingestedUntil(builder, 200),
        repl1 = StatusUtil.replicated(builder, 50), repl2 = StatusUtil.replicated(builder, 150);

    Status order1 = combiner.typedReduce(key,
        Arrays.asList(newFile, update1, repl1, update2, repl2).iterator());

    // Got all replication updates before ingest updates
    Status permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, repl1, update1, repl2, update2).iterator());

    assertEquals(order1, permutation);

    // All replications before updates
    permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, repl1, repl2, update1, update2).iterator());

    assertEquals(order1, permutation);

    // All updates before replications
    permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, update1, update2, repl1, repl2).iterator());

    assertEquals(order1, permutation);
  }

  @Test
  public void duplicateStatuses() {
    Status newFile = StatusUtil.fileCreated(100), update1 = StatusUtil.ingestedUntil(builder, 100),
        update2 = StatusUtil.ingestedUntil(builder, 200),
        repl1 = StatusUtil.replicated(builder, 50), repl2 = StatusUtil.replicated(builder, 150);

    Status order1 = combiner.typedReduce(key,
        Arrays.asList(newFile, update1, repl1, update2, repl2).iterator());

    // Repeat the same thing more than once
    Status permutation = combiner.typedReduce(key,
        Arrays.asList(newFile, repl1, update1, update1, repl2, update2, update2).iterator());

    assertEquals(order1, permutation);
  }

  @Test
  public void fileClosedTimePropagated() {
    Status stat1 = Status.newBuilder().setBegin(10).setEnd(20).setClosed(true).setInfiniteEnd(false)
        .setCreatedTime(50).build();
    Status stat2 =
        Status.newBuilder().setBegin(10).setEnd(20).setClosed(true).setInfiniteEnd(false).build();

    Status combined = combiner.typedReduce(key, Arrays.asList(stat1, stat2).iterator());

    assertEquals(stat1, combined);
  }

  @Test
  public void fileClosedTimeChoosesEarliestIgnoringDefault() {
    Status stat1 = Status.newBuilder().setBegin(10).setEnd(20).setClosed(true).setInfiniteEnd(false)
        .setCreatedTime(50).build();
    Status stat2 = Status.newBuilder().setBegin(10).setEnd(20).setClosed(true).setInfiniteEnd(false)
        .setCreatedTime(100).build();

    Status combined = combiner.typedReduce(key, Arrays.asList(stat1, stat2).iterator());

    assertEquals(stat1, combined);

    Status stat3 = Status.newBuilder().setBegin(10).setEnd(20).setClosed(true).setInfiniteEnd(false)
        .setCreatedTime(100).build();

    Status combined2 = combiner.typedReduce(key, Arrays.asList(combined, stat3).iterator());

    assertEquals(combined, combined2);
  }

  @Test
  public void testCombination() {
    List<Status> status = new ArrayList<>();
    long time = System.currentTimeMillis();

    status.add(StatusUtil.fileCreated(time));
    status.add(StatusUtil.openWithUnknownLength());
    status.add(StatusUtil.fileClosed());

    Status combined = combiner.typedReduce(new Key("row"), status.iterator());

    assertEquals(time, combined.getCreatedTime());
    assertTrue(combined.getInfiniteEnd());
    assertTrue(combined.getClosed());
  }
}
