/*
 * Copyright 2010-2014 Ning, Inc.
 * Copyright 2014-2020 Groupon, Inc
 * Copyright 2020-2020 Equinix, Inc
 * Copyright 2014-2020 The Billing Project, LLC
 *
 * The Billing Project 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.killbill.automaton;

import org.killbill.commons.utils.io.Resources;
import org.killbill.xmlloader.XMLLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;

public class TestStateMachine {

    private final Logger logger = LoggerFactory.getLogger(TestStateMachine.class);

    @Test(groups = "fast")
    public void testStateMachine() throws Exception {
        final DefaultStateMachineConfig sms = XMLLoader.getObjectFromString(Resources.getResource("org/killbill/automaton/PaymentStates.xml").toExternalForm(), DefaultStateMachineConfig.class);

        Assert.assertEquals(sms.getStateMachines().length, 2);

        final DefaultStateMachine sm1 = sms.getStateMachines()[0];

        Assert.assertEquals(sm1.getStates().length, 3);
        Assert.assertEquals(sm1.getStates()[0].getName(), "AUTH_INIT");
        Assert.assertEquals(sm1.getStates()[1].getName(), "AUTH_SUCCESS");
        Assert.assertEquals(sm1.getStates()[2].getName(), "AUTH_FAILED");

        Assert.assertEquals(sm1.getOperations().length, 1);
        Assert.assertEquals(sm1.getOperations()[0].getName(), "OP_AUTHORIZE");

        Assert.assertEquals(sm1.getTransitions().length, 2);
        Assert.assertEquals(sm1.getTransitions()[0].getInitialState().getName(), "AUTH_INIT");
        Assert.assertEquals(sm1.getTransitions()[0].getOperation().getName(), "OP_AUTHORIZE");
        Assert.assertEquals(sm1.getTransitions()[0].getOperationResult(), OperationResult.SUCCESS);
        Assert.assertEquals(sm1.getTransitions()[0].getFinalState().getName(), "AUTH_SUCCESS");

        Assert.assertEquals(sm1.getTransitions()[1].getInitialState().getName(), "AUTH_INIT");
        Assert.assertEquals(sm1.getTransitions()[1].getOperation().getName(), "OP_AUTHORIZE");
        Assert.assertEquals(sm1.getTransitions()[1].getOperationResult(), OperationResult.FAILURE);
        Assert.assertEquals(sm1.getTransitions()[1].getFinalState().getName(), "AUTH_FAILED");

        final DefaultStateMachine sm2 = sms.getStateMachines()[1];

        Assert.assertEquals(sm2.getStates().length, 4);
        Assert.assertEquals(sm2.getStates()[0].getName(), "CAPTURE_INIT");
        Assert.assertEquals(sm2.getStates()[1].getName(), "CAPTURE_SUCCESS");
        Assert.assertEquals(sm2.getStates()[2].getName(), "CAPTURE_FAILED");
        Assert.assertEquals(sm2.getStates()[3].getName(), "CAPTURE_ERRORED");

        Assert.assertEquals(sm2.getOperations().length, 1);
        Assert.assertEquals(sm2.getOperations()[0].getName(), "OP_CAPTURE");

        Assert.assertEquals(sm2.getTransitions().length, 3);
        Assert.assertEquals(sm2.getTransitions()[0].getInitialState().getName(), "CAPTURE_INIT");
        Assert.assertEquals(sm2.getTransitions()[0].getOperation().getName(), "OP_CAPTURE");
        Assert.assertEquals(sm2.getTransitions()[0].getOperationResult(), OperationResult.SUCCESS);
        Assert.assertEquals(sm2.getTransitions()[0].getFinalState().getName(), "CAPTURE_SUCCESS");

        Assert.assertEquals(sm2.getTransitions()[1].getInitialState().getName(), "CAPTURE_INIT");
        Assert.assertEquals(sm2.getTransitions()[1].getOperation().getName(), "OP_CAPTURE");
        Assert.assertEquals(sm2.getTransitions()[1].getOperationResult(), OperationResult.FAILURE);
        Assert.assertEquals(sm2.getTransitions()[1].getFinalState().getName(), "CAPTURE_FAILED");

        Assert.assertEquals(sm2.getTransitions()[2].getInitialState().getName(), "CAPTURE_INIT");
        Assert.assertEquals(sm2.getTransitions()[2].getOperation().getName(), "OP_CAPTURE");
        Assert.assertEquals(sm2.getTransitions()[2].getOperationResult(), OperationResult.EXCEPTION);
        Assert.assertEquals(sm2.getTransitions()[2].getFinalState().getName(), "CAPTURE_ERRORED");

        Assert.assertEquals(sms.getLinkStateMachines().length, 1);

        Assert.assertEquals(sms.getLinkStateMachines()[0].getInitialState().getName(), "AUTH_SUCCESS");
        Assert.assertEquals(sms.getLinkStateMachines()[0].getInitialStateMachine().getName(), "AUTHORIZE");

        Assert.assertEquals(sms.getLinkStateMachines()[0].getFinalState().getName(), "CAPTURE_INIT");
        Assert.assertEquals(sms.getLinkStateMachines()[0].getFinalStateMachine().getName(), "CAPTURE");

    }

    @Test(groups = "fast")
    public void testStateTransition() throws Exception {
        final DefaultStateMachineConfig sms = XMLLoader.getObjectFromString(Resources.getResource("org/killbill/automaton/PaymentStates.xml").toExternalForm(), DefaultStateMachineConfig.class);

        final DefaultStateMachine sm1 = sms.getStateMachines()[1];
        final State state = sm1.getState("CAPTURE_INIT");
        final Operation operation = sm1.getOperation("OP_CAPTURE");

        state.runOperation(operation,
                           new Operation.OperationCallback() {
                               @Override
                               public OperationResult doOperationCallback() throws OperationException {
                                   return OperationResult.SUCCESS;
                               }
                           },
                           new State.EnteringStateCallback() {
                               @Override
                               public void enteringState(final State newState, final Operation.OperationCallback operationCallback, final OperationResult operationResult, final State.LeavingStateCallback leavingStateCallback) {
                                   logger.info("Entering state " + newState.getName());
                                   Assert.assertEquals(newState.getName(), "CAPTURE_SUCCESS");
                               }
                           },
                           new State.LeavingStateCallback() {
                               @Override
                               public void leavingState(final State oldState) {
                                   logger.info("Leaving state " + oldState.getName());
                                   Assert.assertEquals(oldState.getName(), "CAPTURE_INIT");
                               }
                           }
                          );

        state.runOperation(operation,
                           new Operation.OperationCallback() {
                               @Override
                               public OperationResult doOperationCallback() throws OperationException {
                                   return OperationResult.FAILURE;
                               }
                           },
                           new State.EnteringStateCallback() {
                               @Override
                               public void enteringState(final State newState, final Operation.OperationCallback operationCallback, final OperationResult operationResult, final State.LeavingStateCallback leavingStateCallback) {
                                   logger.info("Entering state " + newState.getName());
                                   Assert.assertEquals(newState.getName(), "CAPTURE_FAILED");
                               }
                           },
                           new State.LeavingStateCallback() {
                               @Override
                               public void leavingState(final State oldState) {
                                   logger.info("Leaving state " + oldState.getName());
                                   Assert.assertEquals(oldState.getName(), "CAPTURE_INIT");
                               }
                           }
                          );

        try {
            state.runOperation(operation,
                               new Operation.OperationCallback() {
                                   @Override
                                   public OperationResult doOperationCallback() throws OperationException {
                                       throw new OperationException();
                                   }
                               },
                               new State.EnteringStateCallback() {
                                   @Override
                                   public void enteringState(final State newState, final Operation.OperationCallback operationCallback, final OperationResult operationResult, final State.LeavingStateCallback leavingStateCallback) {
                                       logger.info("Entering state " + newState.getName());
                                       Assert.assertEquals(newState.getName(), "CAPTURE_ERRORED");
                                   }
                               },
                               new State.LeavingStateCallback() {
                                   @Override
                                   public void leavingState(final State oldState) {
                                       logger.info("Leaving state " + oldState.getName());
                                       Assert.assertEquals(oldState.getName(), "CAPTURE_INIT");
                                   }
                               }
                              );
            Assert.fail("Should throw an exception");
        } catch (final OperationException e) {
            Assert.assertTrue(true);
        }
    }

    @Test(groups = "fast")
    public void testLinkedStateMachines() throws Exception {

        final DefaultStateMachineConfig sms = XMLLoader.getObjectFromString(Resources.getResource("org/killbill/automaton/PaymentStates.xml").toExternalForm(), DefaultStateMachineConfig.class);

        final DefaultStateMachine sm1 = sms.getStateMachines()[0];
        final State state = sm1.getState("AUTH_SUCCESS");

        final DefaultStateMachine sm2 = sms.getStateMachines()[1];
        final Operation operation = sm2.getOperation("OP_CAPTURE");

        state.runOperation(operation,
                           new Operation.OperationCallback() {
                               @Override
                               public OperationResult doOperationCallback() throws OperationException {
                                   return OperationResult.SUCCESS;
                               }
                           },
                           new State.EnteringStateCallback() {
                               @Override
                               public void enteringState(final State newState, final Operation.OperationCallback operationCallback, final OperationResult operationResult, final State.LeavingStateCallback leavingStateCallback) {
                                   logger.info("Entering state " + newState.getName());
                                   Assert.assertEquals(newState.getName(), "CAPTURE_SUCCESS");
                               }
                           },
                           new State.LeavingStateCallback() {
                               @Override
                               public void leavingState(final State oldState) {
                                   logger.info("Leaving state " + oldState.getName());
                                   Assert.assertEquals(oldState.getName(), "CAPTURE_INIT");
                               }
                           }
                          );
    }
}
