package com.ruoyi.generator.service.front;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
import com.ruoyi.generator.domain.front.dto.BusinessLoopAuditResult;
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;
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;

public class BusinessLoopCoverageValidatorTest
{
    private final BusinessLoopCoverageValidator validator = new BusinessLoopCoverageValidator();

    @Test
    public void databaseAuditPassesWhenTablesAndFieldsCoverPlan()
    {
        BusinessLoopAuditResult result = validator.auditDatabase(completePlan(), completeDatabase());

        assertTrue(result.isComplete());
        assertTrue(result.getErrors().isEmpty());
        assertEquals(1, result.getLoopActions());
        assertEquals(2, result.getStateTransitions());
        assertEquals(1, result.getQuantityRules());
        assertEquals(1, result.getRecordRules());
    }

    @Test
    public void databaseAuditFailsWhenQuantityFieldMissing()
    {
        DatabaseDesignResponse database = completeDatabase();
        database.getTables().get(1).setColumns(Collections.<DatabaseColumnDesign>emptyList());

        BusinessLoopAuditResult result = validator.auditDatabase(completePlan(), database);

        assertFalse(result.isComplete());
        assertTrue(contains(result.getErrors().toString(), "available_count"));
    }

    @Test
    public void businessActionAuditFailsWhenRequiredActionMissing()
    {
        BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(),
                Collections.<BusinessActionDesign>emptyList());

        assertFalse(result.isComplete());
        assertTrue(result.getMissingRequiredActions().contains("confirm_borrow"));
    }

    @Test
    public void businessActionAuditPassesWhenRequiredStatusAndQuantityEffectsExist()
    {
        BusinessActionDesign action = new BusinessActionDesign();
        action.setCode("confirm_borrow");
        action.setEffects(Arrays.asList(
                effect("SET_STATUS", "borrow_order", "borrow_status"),
                effect("DECREASE_NUMBER", "book_stock", "available_count"),
                effect("INSERT_ROW", "borrow_record", null)));

        BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(), Arrays.asList(action));

        assertTrue(result.isComplete());
        assertTrue(result.getMissingRequiredActions().isEmpty());
        assertTrue(result.getErrors().isEmpty());
    }

    @Test
    public void businessActionAuditFailsWhenTopLevelRecordRuleMissingEffect()
    {
        BusinessActionDesign action = new BusinessActionDesign();
        action.setCode("confirm_borrow");
        action.setEffects(Arrays.asList(
                effect("SET_STATUS", "borrow_order", "borrow_status"),
                effect("DECREASE_NUMBER", "book_stock", "available_count")));

        BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(), Arrays.asList(action));

        assertFalse(result.isComplete());
        assertTrue(contains(result.getErrors().toString(), "record effect")
                || contains(result.getErrors().toString(), "confirm_borrow"));
    }

    @Test
    public void businessActionAuditFailsWhenTopLevelQuantityRuleMatchesWrongField()
    {
        BusinessLoopPlan plan = completePlan();
        plan.getActions().get(0).setQuantityEffects(Collections.<BusinessLoopQuantityEffect>emptyList());
        BusinessActionDesign action = new BusinessActionDesign();
        action.setCode("confirm_borrow");
        action.setEffects(Arrays.asList(
                effect("SET_STATUS", "borrow_order", "borrow_status"),
                effect("DECREASE_NUMBER", "book_stock", "borrowed_count"),
                effect("INSERT_ROW", "borrow_record", null)));

        BusinessLoopAuditResult result = validator.auditBusinessActions(plan, Arrays.asList(action));

        assertFalse(result.isComplete());
        assertTrue(contains(result.getErrors().toString(), "available_count")
                || contains(result.getErrors().toString(), "quantity effect"));
    }

    @Test
    public void businessActionAuditFailsWhenStateStatusEffectMatchesWrongField()
    {
        BusinessLoopPlan plan = completePlan();
        plan.getActions().get(0).setStateTransitions(Collections.<BusinessLoopStateTransition>emptyList());
        BusinessActionDesign action = new BusinessActionDesign();
        action.setCode("confirm_borrow");
        action.setEffects(Arrays.asList(
                effect("SET_STATUS", "borrow_order", "state"),
                effect("DECREASE_NUMBER", "book_stock", "available_count"),
                effect("INSERT_ROW", "borrow_record", null)));

        BusinessLoopAuditResult result = validator.auditBusinessActions(plan, Arrays.asList(action));

        assertFalse(result.isComplete());
        assertTrue(contains(result.getErrors().toString(), "borrow_status")
                || contains(result.getErrors().toString(), "status effect"));
    }

    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()));
        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()
    {
        BusinessLoopAction action = new BusinessLoopAction();
        action.setCode("confirm_borrow");
        action.setName("Confirm borrow");
        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 DatabaseDesignResponse completeDatabase()
    {
        DatabaseDesignResponse response = new DatabaseDesignResponse();
        response.setTables(Arrays.asList(
                table("borrow_order", "id", "borrow_status"),
                table("book_stock", "id", "available_count"),
                table("borrow_record", "id")));
        return response;
    }

    private DatabaseTableDesign table(String tableName, String... columnNames)
    {
        DatabaseTableDesign table = new DatabaseTableDesign();
        table.setTableName(tableName);
        DatabaseColumnDesign[] columns = new DatabaseColumnDesign[columnNames.length];
        for (int i = 0; i < columnNames.length; i++)
        {
            columns[i] = column(columnNames[i]);
        }
        table.setColumns(Arrays.asList(columns));
        return table;
    }

    private DatabaseColumnDesign column(String columnName)
    {
        DatabaseColumnDesign column = new DatabaseColumnDesign();
        column.setColumnName(columnName);
        return column;
    }

    private BusinessActionEffectDesign effect(String type)
    {
        return effect(type, null, null);
    }

    private BusinessActionEffectDesign effect(String type, String targetTable, String targetField)
    {
        BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
        effect.setType(type);
        effect.setTargetTable(targetTable);
        effect.setTargetField(targetField);
        return effect;
    }

    private boolean contains(String text, String needle)
    {
        return text != null && text.contains(needle);
    }
}
