package com.ruoyi.generator.service.front;

import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopAction;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopObject;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopQuantityEffect;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopRecordEffect;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateMachine;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateTransition;

public class BusinessLoopPlanValidatorTest
{
    private final BusinessLoopPlanValidator validator = new BusinessLoopPlanValidator();

    @Test
    public void acceptsCompleteLoopPlan()
    {
        validator.validate(completePlan());
    }

    @Test
    public void rejectsPlanWithoutStateTransition()
    {
        BusinessLoopPlan plan = completePlan();
        plan.getStateMachines().get(0).setTransitions(Collections.<BusinessLoopStateTransition>emptyList());
        plan.getActions().get(0).setStateTransitions(Collections.<BusinessLoopStateTransition>emptyList());

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                validator.validate(plan);
            }
        });

        assertTrue(exception.getMessage().contains("state transition"));
    }

    @Test
    public void rejectsPlanWithoutQuantityOrRecordRule()
    {
        BusinessLoopPlan plan = completePlan();
        plan.setQuantityRules(Collections.<BusinessLoopQuantityEffect>emptyList());
        plan.setRecordRules(Collections.<BusinessLoopRecordEffect>emptyList());

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                validator.validate(plan);
            }
        });

        assertTrue(exception.getMessage().contains("quantity or record"));
    }

    @Test
    public void rejectsDuplicatedActionCode()
    {
        BusinessLoopPlan plan = completePlan();
        plan.setActions(Arrays.asList(requiredAction("confirm_borrow"), requiredAction("confirm_borrow")));

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                validator.validate(plan);
            }
        });

        assertTrue(exception.getMessage().contains("duplicated"));
    }

    @Test
    public void rejectsPlanWithoutRequiredAction()
    {
        BusinessLoopPlan plan = completePlan();
        plan.getActions().get(0).setRequired(false);

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                validator.validate(plan);
            }
        });

        assertTrue(exception.getMessage().contains("required action"));
    }

    private BusinessLoopPlan completePlan()
    {
        BusinessLoopPlan plan = new BusinessLoopPlan();
        plan.setDomainName("library");
        plan.setCoreObjects(Arrays.asList(coreObject()));
        plan.setStateMachines(Arrays.asList(stateMachine()));
        plan.setActions(Arrays.asList(requiredAction("confirm_borrow")));
        plan.setQuantityRules(Arrays.asList(quantityRule()));
        plan.setRecordRules(Arrays.asList(recordRule()));
        return plan;
    }

    private BusinessLoopObject coreObject()
    {
        BusinessLoopObject object = new BusinessLoopObject();
        object.setCode("borrow_order");
        object.setName("Borrow order");
        object.setTableName("borrow_order");
        return object;
    }

    private BusinessLoopStateMachine stateMachine()
    {
        BusinessLoopStateMachine stateMachine = new BusinessLoopStateMachine();
        stateMachine.setObjectCode("borrow_order");
        stateMachine.setTableName("borrow_order");
        stateMachine.setStatusField("borrow_status");
        stateMachine.setStates(Arrays.asList("submitted", "borrowed"));
        stateMachine.setTransitions(Arrays.asList(transition()));
        return stateMachine;
    }

    private BusinessLoopAction requiredAction(String code)
    {
        BusinessLoopAction action = new BusinessLoopAction();
        action.setCode(code);
        action.setName("Confirm borrow");
        action.setActor("librarian");
        action.setOwnerObject("borrow_order");
        action.setOwnerTable("borrow_order");
        action.setRequired(true);
        action.setStateTransitions(Arrays.asList(transition()));
        action.setQuantityEffects(Arrays.asList(quantityRule()));
        return action;
    }

    private BusinessLoopStateTransition transition()
    {
        BusinessLoopStateTransition transition = new BusinessLoopStateTransition();
        transition.setActionCode("confirm_borrow");
        transition.setFromState("submitted");
        transition.setToState("borrowed");
        return transition;
    }

    private BusinessLoopQuantityEffect quantityRule()
    {
        BusinessLoopQuantityEffect effect = new BusinessLoopQuantityEffect();
        effect.setActionCode("confirm_borrow");
        effect.setObjectCode("book_stock");
        effect.setTableName("book_stock");
        effect.setQuantityField("available_count");
        effect.setEffectType("decrease");
        effect.setAmount("1");
        return effect;
    }

    private BusinessLoopRecordEffect recordRule()
    {
        BusinessLoopRecordEffect effect = new BusinessLoopRecordEffect();
        effect.setActionCode("confirm_borrow");
        effect.setObjectCode("borrow_record");
        effect.setTableName("borrow_record");
        effect.setOperation("insert");
        return effect;
    }

    private ServiceException expectServiceException(ThrowingRunnable runnable)
    {
        try
        {
            runnable.run();
        }
        catch (ServiceException e)
        {
            return e;
        }
        throw new AssertionError("Expected ServiceException");
    }

    private interface ThrowingRunnable
    {
        void run();
    }
}
