/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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
 *
 *    http://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.elasticsearch.rest.action.cat;

import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.action.admin.indices.recovery.RecoveryResponse;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.RecoverySource;
import org.elasticsearch.cluster.routing.RecoverySource.SnapshotRecoverySource;
import org.elasticsearch.cluster.routing.TestShardRouting;
import org.elasticsearch.common.Randomness;
import org.elasticsearch.common.Table;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.indices.recovery.RecoveryState;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.test.ESTestCase;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static org.elasticsearch.mock.orig.Mockito.when;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.mockito.Mockito.mock;

public class RestRecoveryActionTests extends ESTestCase {

    public void testRestRecoveryAction() {
        final Settings settings = Settings.EMPTY;
        final RestController restController = new RestController(settings, Collections.emptySet(), null, null, null);
        final RestRecoveryAction action = new RestRecoveryAction(settings, restController);
        final int totalShards = randomIntBetween(1, 32);
        final int successfulShards = Math.max(0, totalShards - randomIntBetween(1, 2));
        final int failedShards = totalShards - successfulShards;
        final boolean detailed = randomBoolean();
        final Map<String, List<RecoveryState>> shardRecoveryStates = new HashMap<>();
        final List<RecoveryState> recoveryStates = new ArrayList<>();

        for (int i = 0; i < successfulShards; i++) {
            final RecoveryState state = mock(RecoveryState.class);
            when(state.getShardId()).thenReturn(new ShardId(new Index("index", "_na_"), i));
            final RecoveryState.Timer timer = mock(RecoveryState.Timer.class);
            when(timer.time()).thenReturn((long)randomIntBetween(1000000, 10 * 1000000));
            when(state.getTimer()).thenReturn(timer);
            when(state.getRecoverySource()).thenReturn(TestShardRouting.randomRecoverySource());
            when(state.getStage()).thenReturn(randomFrom(RecoveryState.Stage.values()));
            final DiscoveryNode sourceNode = randomBoolean() ? mock(DiscoveryNode.class) : null;
            if (sourceNode != null) {
                when(sourceNode.getHostName()).thenReturn(randomAlphaOfLength(8));
            }
            when(state.getSourceNode()).thenReturn(sourceNode);
            final DiscoveryNode targetNode = mock(DiscoveryNode.class);
            when(targetNode.getHostName()).thenReturn(randomAlphaOfLength(8));
            when(state.getTargetNode()).thenReturn(targetNode);

            RecoveryState.Index index = mock(RecoveryState.Index.class);

            final int totalRecoveredFiles = randomIntBetween(1, 64);
            when(index.totalRecoverFiles()).thenReturn(totalRecoveredFiles);
            final int recoveredFileCount = randomIntBetween(0, totalRecoveredFiles);
            when(index.recoveredFileCount()).thenReturn(recoveredFileCount);
            when(index.recoveredFilesPercent()).thenReturn((100f * recoveredFileCount) / totalRecoveredFiles);
            when(index.totalFileCount()).thenReturn(randomIntBetween(totalRecoveredFiles, 2 * totalRecoveredFiles));

            final int totalRecoveredBytes = randomIntBetween(1, 1 << 24);
            when(index.totalRecoverBytes()).thenReturn((long)totalRecoveredBytes);
            final int recoveredBytes = randomIntBetween(0, totalRecoveredBytes);
            when(index.recoveredBytes()).thenReturn((long)recoveredBytes);
            when(index.recoveredBytesPercent()).thenReturn((100f * recoveredBytes) / totalRecoveredBytes);
            when(index.totalRecoverBytes()).thenReturn((long)randomIntBetween(totalRecoveredBytes, 2 * totalRecoveredBytes));
            when(state.getIndex()).thenReturn(index);

            final RecoveryState.Translog translog = mock(RecoveryState.Translog.class);
            final int translogOps = randomIntBetween(0, 1 << 18);
            when(translog.totalOperations()).thenReturn(translogOps);
            final int translogOpsRecovered = randomIntBetween(0, translogOps);
            when(translog.recoveredOperations()).thenReturn(translogOpsRecovered);
            when(translog.recoveredPercent()).thenReturn(translogOps == 0 ? 100f : (100f * translogOpsRecovered / translogOps));
            when(state.getTranslog()).thenReturn(translog);

            recoveryStates.add(state);
        }

        final List<RecoveryState> shuffle = new ArrayList<>(recoveryStates);
        Randomness.shuffle(shuffle);
        shardRecoveryStates.put("index", shuffle);

        final List<ShardOperationFailedException> shardFailures = new ArrayList<>();
        final RecoveryResponse response = new RecoveryResponse(
                totalShards,
                successfulShards,
                failedShards,
                detailed,
                shardRecoveryStates,
                shardFailures);
        final Table table = action.buildRecoveryTable(null, response);

        assertNotNull(table);

        List<Table.Cell> headers = table.getHeaders();

        assertThat(headers.get(0).value, equalTo("index"));
        assertThat(headers.get(1).value, equalTo("shard"));
        assertThat(headers.get(2).value, equalTo("time"));
        assertThat(headers.get(3).value, equalTo("type"));
        assertThat(headers.get(4).value, equalTo("stage"));
        assertThat(headers.get(5).value, equalTo("source_host"));
        assertThat(headers.get(6).value, equalTo("source_node"));
        assertThat(headers.get(7).value, equalTo("target_host"));
        assertThat(headers.get(8).value, equalTo("target_node"));
        assertThat(headers.get(9).value, equalTo("repository"));
        assertThat(headers.get(10).value, equalTo("snapshot"));
        assertThat(headers.get(11).value, equalTo("files"));
        assertThat(headers.get(12).value, equalTo("files_recovered"));
        assertThat(headers.get(13).value, equalTo("files_percent"));
        assertThat(headers.get(14).value, equalTo("files_total"));
        assertThat(headers.get(15).value, equalTo("bytes"));
        assertThat(headers.get(16).value, equalTo("bytes_recovered"));
        assertThat(headers.get(17).value, equalTo("bytes_percent"));
        assertThat(headers.get(18).value, equalTo("bytes_total"));
        assertThat(headers.get(19).value, equalTo("translog_ops"));
        assertThat(headers.get(20).value, equalTo("translog_ops_recovered"));
        assertThat(headers.get(21).value, equalTo("translog_ops_percent"));

        assertThat(table.getRows().size(), equalTo(successfulShards));
        for (int i = 0; i < successfulShards; i++) {
            final RecoveryState state = recoveryStates.get(i);
            List<Table.Cell> cells = table.getRows().get(i);
            assertThat(cells.get(0).value, equalTo("index"));
            assertThat(cells.get(1).value, equalTo(i));
            assertThat(cells.get(2).value, equalTo(new TimeValue(state.getTimer().time())));
            assertThat(cells.get(3).value, equalTo(state.getRecoverySource().getType().name().toLowerCase(Locale.ROOT)));
            assertThat(cells.get(4).value, equalTo(state.getStage().name().toLowerCase(Locale.ROOT)));
            assertThat(cells.get(5).value, equalTo(state.getSourceNode() == null ? "n/a" : state.getSourceNode().getHostName()));
            assertThat(cells.get(6).value, equalTo(state.getSourceNode() == null ? "n/a" : state.getSourceNode().getName()));
            assertThat(cells.get(7).value, equalTo(state.getTargetNode().getHostName()));
            assertThat(cells.get(8).value, equalTo(state.getTargetNode().getName()));
            assertThat(
                    cells.get(9).value,
                    equalTo(state.getRecoverySource() == null || state.getRecoverySource().getType() != RecoverySource.Type.SNAPSHOT ?
                        "n/a" :
                        ((SnapshotRecoverySource) state.getRecoverySource()).snapshot().getRepository()));
            assertThat(
                    cells.get(10).value,
                    equalTo(state.getRecoverySource() == null || state.getRecoverySource().getType() != RecoverySource.Type.SNAPSHOT ?
                        "n/a" :
                        ((SnapshotRecoverySource) state.getRecoverySource()).snapshot().getSnapshotId().getName()));
            assertThat(cells.get(11).value, equalTo(state.getIndex().totalRecoverFiles()));
            assertThat(cells.get(12).value, equalTo(state.getIndex().recoveredFileCount()));
            assertThat(cells.get(13).value, equalTo(percent(state.getIndex().recoveredFilesPercent())));
            assertThat(cells.get(14).value, equalTo(state.getIndex().totalFileCount()));
            assertThat(cells.get(15).value, equalTo(state.getIndex().totalRecoverBytes()));
            assertThat(cells.get(16).value, equalTo(state.getIndex().recoveredBytes()));
            assertThat(cells.get(17).value, equalTo(percent(state.getIndex().recoveredBytesPercent())));
            assertThat(cells.get(18).value, equalTo(state.getIndex().totalBytes()));
            assertThat(cells.get(19).value, equalTo(state.getTranslog().totalOperations()));
            assertThat(cells.get(20).value, equalTo(state.getTranslog().recoveredOperations()));
            assertThat(cells.get(21).value, equalTo(percent(state.getTranslog().recoveredPercent())));
        }
    }

    private static String percent(float percent) {
        return String.format(Locale.ROOT, "%1.1f%%", percent);
    }

}
