/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

/*
 * 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.
 */
/*
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

package org.opensearch.common.util.concurrent;

import org.apache.logging.log4j.Logger;
import org.opensearch.common.SuppressLoggerChecks;
import org.opensearch.common.component.Lifecycle;
import org.opensearch.test.OpenSearchTestCase;
import org.mockito.InOrder;

import java.util.concurrent.Callable;

import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

/**
 * Tests {@link AbstractLifecycleRunnable}.
 */
public class AbstractLifecycleRunnableTests extends OpenSearchTestCase {
    private final Lifecycle lifecycle = mock(Lifecycle.class);
    private final Logger logger = mock(Logger.class);

    public void testDoRunOnlyRunsWhenNotStoppedOrClosed() throws Exception {
        Callable<?> runCallable = mock(Callable.class);

        // it's "not stopped or closed"
        when(lifecycle.stoppedOrClosed()).thenReturn(false);

        AbstractLifecycleRunnable runnable = new AbstractLifecycleRunnable(lifecycle, logger) {
            @Override
            public void onFailure(Exception e) {
                fail("It should not fail");
            }

            @Override
            protected void doRunInLifecycle() throws Exception {
                runCallable.call();
            }
        };

        runnable.run();

        InOrder inOrder = inOrder(lifecycle, logger, runCallable);

        inOrder.verify(lifecycle).stoppedOrClosed();
        inOrder.verify(runCallable).call();
        inOrder.verify(lifecycle).stoppedOrClosed(); // onAfter uses it too, but we're not testing it here
        inOrder.verifyNoMoreInteractions();
    }

    @SuppressLoggerChecks(reason = "mock usage")
    public void testDoRunDoesNotRunWhenStoppedOrClosed() throws Exception {
        Callable<?> runCallable = mock(Callable.class);

        // it's stopped or closed
        when(lifecycle.stoppedOrClosed()).thenReturn(true);

        AbstractLifecycleRunnable runnable = new AbstractLifecycleRunnable(lifecycle, logger) {
            @Override
            public void onFailure(Exception e) {
                fail("It should not fail");
            }

            @Override
            protected void doRunInLifecycle() throws Exception {
                fail("Should not run with lifecycle stopped or closed.");
            }
        };

        runnable.run();

        InOrder inOrder = inOrder(lifecycle, logger, runCallable);

        inOrder.verify(lifecycle).stoppedOrClosed();
        inOrder.verify(logger).trace(anyString());
        inOrder.verify(lifecycle).stoppedOrClosed(); // onAfter uses it too, but we're not testing it here
        inOrder.verifyNoMoreInteractions();
    }

    public void testOnAfterOnlyWhenNotStoppedOrClosed() throws Exception {
        Callable<?> runCallable = mock(Callable.class);
        Callable<?> afterCallable = mock(Callable.class);

        // it's "not stopped or closed"
        when(lifecycle.stoppedOrClosed()).thenReturn(false);

        AbstractLifecycleRunnable runnable = new AbstractLifecycleRunnable(lifecycle, logger) {
            @Override
            public void onFailure(Exception e) {
                fail("It should not fail");
            }

            @Override
            protected void doRunInLifecycle() throws Exception {
                runCallable.call();
            }

            @Override
            protected void onAfterInLifecycle() {
                try {
                    afterCallable.call();
                } catch (Exception e) {
                    fail("Unexpected for mock.");
                }
            }
        };

        runnable.run();

        InOrder inOrder = inOrder(lifecycle, logger, runCallable, afterCallable);

        inOrder.verify(lifecycle).stoppedOrClosed();
        inOrder.verify(runCallable).call();
        inOrder.verify(lifecycle).stoppedOrClosed();
        inOrder.verify(afterCallable).call();
        inOrder.verifyNoMoreInteractions();
    }

    public void testOnAfterDoesNotHappenWhenStoppedOrClosed() throws Exception {
        Callable<?> runCallable = mock(Callable.class);

        // it's stopped or closed
        when(lifecycle.stoppedOrClosed()).thenReturn(true);

        AbstractLifecycleRunnable runnable = new AbstractLifecycleRunnable(lifecycle, logger) {
            @Override
            public void onFailure(Exception e) {
                fail("It should not fail");
            }

            @Override
            protected void doRunInLifecycle() throws Exception {
                fail("Should not run with lifecycle stopped or closed.");
            }

            @Override
            protected void onAfterInLifecycle() {
                fail("Should not run with lifecycle stopped or closed.");
            }
        };

        runnable.run();

        InOrder inOrder = inOrder(lifecycle, runCallable);

        inOrder.verify(lifecycle, times(2)).stoppedOrClosed();
        inOrder.verifyNoMoreInteractions();
    }
}
