package com.ruoyi.generator.service.front;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.StringUtils;
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.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.DatabaseColumnDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;

@Component
public class BusinessLoopCoverageValidator
{
    public BusinessLoopAuditResult auditDatabase(BusinessLoopPlan plan, DatabaseDesignResponse database)
    {
        BusinessLoopAuditResult result = newResult(plan);
        Map<String, Set<String>> columnsByTable = collectColumnsByTable(database);
        if (plan == null)
        {
            result.getErrors().add("Business loop plan is required");
            finish(result);
            return result;
        }
        for (BusinessLoopStateMachine stateMachine : safe(plan.getStateMachines()))
        {
            if (stateMachine == null)
            {
                continue;
            }
            if (!hasColumn(columnsByTable, stateMachine.getTableName(), stateMachine.getStatusField()))
            {
                result.getErrors().add("Missing state status field: "
                        + formatField(stateMachine.getTableName(), stateMachine.getStatusField()));
            }
        }
        for (BusinessLoopQuantityEffect quantityRule : safe(plan.getQuantityRules()))
        {
            if (quantityRule == null)
            {
                continue;
            }
            if (!hasColumn(columnsByTable, quantityRule.getTableName(), quantityRule.getQuantityField()))
            {
                result.getErrors().add("Missing quantity field: "
                        + formatField(quantityRule.getTableName(), quantityRule.getQuantityField()));
            }
        }
        Set<String> tableNames = columnsByTable.keySet();
        for (BusinessLoopRecordEffect recordRule : safe(plan.getRecordRules()))
        {
            if (recordRule == null)
            {
                continue;
            }
            if (!tableNames.contains(normalize(recordRule.getTableName())))
            {
                result.getErrors().add("Missing record table: " + StringUtils.defaultString(recordRule.getTableName()));
            }
        }
        finish(result);
        return result;
    }

    public BusinessLoopAuditResult auditBusinessActions(BusinessLoopPlan plan, List<BusinessActionDesign> actions)
    {
        BusinessLoopAuditResult result = newResult(plan);
        Map<String, BusinessActionDesign> actionsByCode = collectActionsByCode(actions);
        if (plan == null)
        {
            result.getErrors().add("Business loop plan is required");
            finish(result);
            return result;
        }
        Map<String, List<ExpectedTarget>> stateTargetsByAction = collectStateTargetsByAction(plan);
        Map<String, List<ExpectedTarget>> quantityTargetsByAction = collectQuantityTargetsByAction(plan);
        Map<String, List<ExpectedTarget>> recordTargetsByAction = collectRecordTargetsByAction(plan);
        for (BusinessLoopAction planAction : safe(plan.getActions()))
        {
            if (planAction == null || !planAction.isRequired())
            {
                continue;
            }
            BusinessActionDesign action = actionsByCode.get(planAction.getCode());
            if (action == null)
            {
                result.getMissingRequiredActions().add(planAction.getCode());
                continue;
            }
            validateRequiredEffects(result, planAction, action, stateTargetsByAction, quantityTargetsByAction,
                    recordTargetsByAction);
        }
        finish(result);
        return result;
    }

    private void validateRequiredEffects(BusinessLoopAuditResult result, BusinessLoopAction planAction,
            BusinessActionDesign action, Map<String, List<ExpectedTarget>> stateTargetsByAction,
            Map<String, List<ExpectedTarget>> quantityTargetsByAction,
            Map<String, List<ExpectedTarget>> recordTargetsByAction)
    {
        List<ExpectedTarget> stateTargets = safe(stateTargetsByAction.get(planAction.getCode()));
        List<ExpectedTarget> quantityTargets = safe(quantityTargetsByAction.get(planAction.getCode()));
        List<ExpectedTarget> recordTargets = safe(recordTargetsByAction.get(planAction.getCode()));
        if ((size(planAction.getStateTransitions()) > 0 || !stateTargets.isEmpty())
                && !hasMatchingEffect(action, stateTargets, "SET_STATUS", "UPDATE_FIELDS", "UPDATE_FIELD"))
        {
            result.getErrors().add("Missing status effect for required action " + planAction.getCode()
                    + formatTargets(stateTargets));
        }
        if (!quantityTargets.isEmpty()
                && !hasMatchingEffect(action, quantityTargets, "INCREASE_NUMBER", "DECREASE_NUMBER"))
        {
            result.getErrors().add("Missing quantity effect for required action " + planAction.getCode()
                    + formatTargets(quantityTargets));
        }
        if (!recordTargets.isEmpty() && !hasMatchingEffect(action, recordTargets, "INSERT_ROW", "UPDATE_FIELDS"))
        {
            result.getErrors().add("Missing record effect for required action " + planAction.getCode()
                    + formatTargets(recordTargets));
        }
    }

    private boolean hasMatchingEffect(BusinessActionDesign action, List<ExpectedTarget> expectedTargets, String... types)
    {
        if (action == null || action.getEffects() == null)
        {
            return false;
        }
        if (expectedTargets == null || expectedTargets.isEmpty())
        {
            return hasAnyEffectType(action, types);
        }
        Set<String> expectedTypes = collectExpectedTypes(types);
        for (ExpectedTarget expectedTarget : expectedTargets)
        {
            if (!hasMatchingEffect(action, expectedTarget, expectedTypes))
            {
                return false;
            }
        }
        return true;
    }

    private boolean hasAnyEffectType(BusinessActionDesign action, String... types)
    {
        Set<String> expectedTypes = collectExpectedTypes(types);
        for (BusinessActionEffectDesign effect : action.getEffects())
        {
            if (effect == null)
            {
                continue;
            }
            String type = StringUtils.defaultString(effect.getType()).toUpperCase(Locale.ENGLISH);
            if (expectedTypes.contains(type))
            {
                return true;
            }
        }
        return false;
    }

    private boolean hasMatchingEffect(BusinessActionDesign action, ExpectedTarget expectedTarget,
            Set<String> expectedTypes)
    {
        for (BusinessActionEffectDesign effect : action.getEffects())
        {
            if (effect == null)
            {
                continue;
            }
            String type = StringUtils.defaultString(effect.getType()).toUpperCase(Locale.ENGLISH);
            if (expectedTypes.contains(type) && matchesTarget(effect, expectedTarget))
            {
                return true;
            }
        }
        return false;
    }

    private boolean matchesTarget(BusinessActionEffectDesign effect, ExpectedTarget expectedTarget)
    {
        if (expectedTarget == null)
        {
            return true;
        }
        if (StringUtils.isNotBlank(expectedTarget.getTableName())
                && !normalize(expectedTarget.getTableName()).equals(normalize(effect.getTargetTable())))
        {
            return false;
        }
        if (StringUtils.isBlank(expectedTarget.getFieldName()))
        {
            return true;
        }
        if (normalize(expectedTarget.getFieldName()).equals(normalize(effect.getTargetField())))
        {
            return true;
        }
        return effect.getValues() != null && effect.getValues().containsKey(expectedTarget.getFieldName());
    }

    private Set<String> collectExpectedTypes(String... types)
    {
        Set<String> expectedTypes = new HashSet<String>();
        for (String type : types)
        {
            expectedTypes.add(type);
        }
        return expectedTypes;
    }

    private Map<String, BusinessActionDesign> collectActionsByCode(List<BusinessActionDesign> actions)
    {
        Map<String, BusinessActionDesign> actionsByCode = new HashMap<String, BusinessActionDesign>();
        if (actions == null)
        {
            return actionsByCode;
        }
        for (BusinessActionDesign action : actions)
        {
            if (action != null && StringUtils.isNotBlank(action.getCode()))
            {
                actionsByCode.put(action.getCode(), action);
            }
        }
        return actionsByCode;
    }

    private Map<String, List<ExpectedTarget>> collectStateTargetsByAction(BusinessLoopPlan plan)
    {
        Map<String, List<ExpectedTarget>> targetsByAction = new HashMap<String, List<ExpectedTarget>>();
        for (BusinessLoopStateMachine stateMachine : safe(plan.getStateMachines()))
        {
            if (stateMachine == null)
            {
                continue;
            }
            for (BusinessLoopPlan.BusinessLoopStateTransition transition : safe(stateMachine.getTransitions()))
            {
                if (transition != null && StringUtils.isNotBlank(transition.getActionCode()))
                {
                    addTarget(targetsByAction, transition.getActionCode(),
                            new ExpectedTarget(stateMachine.getTableName(), stateMachine.getStatusField()));
                }
            }
        }
        return targetsByAction;
    }

    private Map<String, List<ExpectedTarget>> collectQuantityTargetsByAction(BusinessLoopPlan plan)
    {
        Map<String, List<ExpectedTarget>> targetsByAction = new HashMap<String, List<ExpectedTarget>>();
        for (BusinessLoopQuantityEffect quantityRule : safe(plan.getQuantityRules()))
        {
            addQuantityTarget(targetsByAction, quantityRule, null);
        }
        for (BusinessLoopAction action : safe(plan.getActions()))
        {
            if (action == null)
            {
                continue;
            }
            for (BusinessLoopQuantityEffect quantityEffect : safe(action.getQuantityEffects()))
            {
                addQuantityTarget(targetsByAction, quantityEffect, action.getCode());
            }
        }
        return targetsByAction;
    }

    private void addQuantityTarget(Map<String, List<ExpectedTarget>> targetsByAction,
            BusinessLoopQuantityEffect quantityEffect, String fallbackActionCode)
    {
        if (quantityEffect == null)
        {
            return;
        }
        String actionCode = StringUtils.isNotBlank(quantityEffect.getActionCode())
                ? quantityEffect.getActionCode() : fallbackActionCode;
        if (StringUtils.isBlank(actionCode))
        {
            return;
        }
        addTarget(targetsByAction, actionCode,
                new ExpectedTarget(quantityEffect.getTableName(), quantityEffect.getQuantityField()));
    }

    private Map<String, List<ExpectedTarget>> collectRecordTargetsByAction(BusinessLoopPlan plan)
    {
        Map<String, List<ExpectedTarget>> targetsByAction = new HashMap<String, List<ExpectedTarget>>();
        for (BusinessLoopRecordEffect recordRule : safe(plan.getRecordRules()))
        {
            addRecordTarget(targetsByAction, recordRule, null);
        }
        for (BusinessLoopAction action : safe(plan.getActions()))
        {
            if (action == null)
            {
                continue;
            }
            for (BusinessLoopRecordEffect recordEffect : safe(action.getRecordEffects()))
            {
                addRecordTarget(targetsByAction, recordEffect, action.getCode());
            }
        }
        return targetsByAction;
    }

    private void addRecordTarget(Map<String, List<ExpectedTarget>> targetsByAction,
            BusinessLoopRecordEffect recordEffect, String fallbackActionCode)
    {
        if (recordEffect == null)
        {
            return;
        }
        String actionCode = StringUtils.isNotBlank(recordEffect.getActionCode())
                ? recordEffect.getActionCode() : fallbackActionCode;
        if (StringUtils.isBlank(actionCode))
        {
            return;
        }
        addTarget(targetsByAction, actionCode, new ExpectedTarget(recordEffect.getTableName(), null));
    }

    private void addTarget(Map<String, List<ExpectedTarget>> targetsByAction, String actionCode,
            ExpectedTarget target)
    {
        List<ExpectedTarget> targets = targetsByAction.get(actionCode);
        if (targets == null)
        {
            targets = new ArrayList<ExpectedTarget>();
            targetsByAction.put(actionCode, targets);
        }
        targets.add(target);
    }

    private Map<String, Set<String>> collectColumnsByTable(DatabaseDesignResponse database)
    {
        Map<String, Set<String>> columnsByTable = new HashMap<String, Set<String>>();
        if (database == null || database.getTables() == null)
        {
            return columnsByTable;
        }
        for (DatabaseTableDesign table : database.getTables())
        {
            if (table == null || StringUtils.isBlank(table.getTableName()))
            {
                continue;
            }
            Set<String> columns = new HashSet<String>();
            if (table.getColumns() != null)
            {
                for (DatabaseColumnDesign column : table.getColumns())
                {
                    if (column != null && StringUtils.isNotBlank(column.getColumnName()))
                    {
                        columns.add(normalize(column.getColumnName()));
                    }
                }
            }
            columnsByTable.put(normalize(table.getTableName()), columns);
        }
        return columnsByTable;
    }

    private boolean hasColumn(Map<String, Set<String>> columnsByTable, String tableName, String columnName)
    {
        Set<String> columns = columnsByTable.get(normalize(tableName));
        return columns != null && columns.contains(normalize(columnName));
    }

    private String formatField(String tableName, String fieldName)
    {
        return StringUtils.defaultString(tableName) + "." + StringUtils.defaultString(fieldName);
    }

    private BusinessLoopAuditResult newResult(BusinessLoopPlan plan)
    {
        BusinessLoopAuditResult result = new BusinessLoopAuditResult();
        result.setLoopActions(size(plan == null ? null : plan.getActions()));
        result.setStateTransitions(countStateTransitions(plan));
        result.setQuantityRules(size(plan == null ? null : plan.getQuantityRules()));
        result.setRecordRules(size(plan == null ? null : plan.getRecordRules()));
        return result;
    }

    private void finish(BusinessLoopAuditResult result)
    {
        result.setComplete(result.getErrors().isEmpty() && result.getMissingRequiredActions().isEmpty());
    }

    private int countStateTransitions(BusinessLoopPlan plan)
    {
        if (plan == null)
        {
            return 0;
        }
        int count = 0;
        for (BusinessLoopStateMachine stateMachine : safe(plan.getStateMachines()))
        {
            if (stateMachine != null)
            {
                count += size(stateMachine.getTransitions());
            }
        }
        for (BusinessLoopAction action : safe(plan.getActions()))
        {
            if (action != null)
            {
                count += size(action.getStateTransitions());
            }
        }
        return count;
    }

    private String normalize(String value)
    {
        return StringUtils.defaultString(value).toLowerCase(Locale.ENGLISH);
    }

    private String formatTargets(List<ExpectedTarget> targets)
    {
        if (targets == null || targets.isEmpty())
        {
            return "";
        }
        StringBuilder builder = new StringBuilder(": ");
        for (int i = 0; i < targets.size(); i++)
        {
            if (i > 0)
            {
                builder.append(", ");
            }
            builder.append(targets.get(i).format());
        }
        return builder.toString();
    }

    private <T> List<T> safe(List<T> items)
    {
        return items == null ? java.util.Collections.<T>emptyList() : items;
    }

    private int size(List<?> items)
    {
        return items == null ? 0 : items.size();
    }

    private static class ExpectedTarget
    {
        private final String tableName;
        private final String fieldName;

        ExpectedTarget(String tableName, String fieldName)
        {
            this.tableName = tableName;
            this.fieldName = fieldName;
        }

        String getTableName()
        {
            return tableName;
        }

        String getFieldName()
        {
            return fieldName;
        }

        String format()
        {
            if (StringUtils.isBlank(fieldName))
            {
                return StringUtils.defaultString(tableName);
            }
            return StringUtils.defaultString(tableName) + "." + fieldName;
        }
    }
}
