package com.ruoyi.generator.service.front;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.domain.GenTable;
import com.ruoyi.generator.domain.GenTableColumn;
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockConfigField;
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockDefinition;
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockInstance;
import com.ruoyi.generator.domain.front.dto.block.PageBusinessBlockLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Service;

@Service
public class BusinessBlockRegistryService
{
    public static final String BUSINESS_BLOCK_CANVAS = "business-blocks-v1";
    public static final String PLACEMENT_PAGE = "page";
    public static final String PLACEMENT_EMBEDDED = "embedded";

    private volatile List<BusinessBlockDefinition> cachedDefinitions;

    @Autowired
    private ChartDatasetValidator chartDatasetValidator;

    public List<BusinessBlockDefinition> listDefinitions()
    {
        if (cachedDefinitions == null)
        {
            cachedDefinitions = loadDefinitions();
        }
        return cachedDefinitions;
    }

    public List<BusinessBlockDefinition> listDefinitions(String surface)
    {
        String normalizedSurface = "admin".equals(surface) ? "admin" : "frontend";
        List<BusinessBlockDefinition> definitions = new ArrayList<BusinessBlockDefinition>();
        for (BusinessBlockDefinition definition : listDefinitions())
        {
            if (definition != null && definition.getSurfaces().contains(normalizedSurface))
            {
                definitions.add(definition);
            }
        }
        return Collections.unmodifiableList(definitions);
    }

    public BusinessBlockDefinition findDefinition(String code)
    {
        for (BusinessBlockDefinition definition : listDefinitions())
        {
            if (definition != null && StringUtils.equals(definition.getCode(), code))
            {
                return definition;
            }
        }
        throw new ServiceException("未知业务块：" + code);
    }

    public PageBusinessBlockLayout parseLayout(String layoutJson)
    {
        if (StringUtils.isEmpty(layoutJson))
        {
            PageBusinessBlockLayout layout = new PageBusinessBlockLayout();
            layout.setCanvas(BUSINESS_BLOCK_CANVAS);
            layout.setBlocks(Collections.<BusinessBlockInstance>emptyList());
            return layout;
        }
        try
        {
            PageBusinessBlockLayout layout = JSON.parseObject(layoutJson, PageBusinessBlockLayout.class);
            if (layout.getBlocks() == null)
            {
                layout.setBlocks(Collections.<BusinessBlockInstance>emptyList());
            }
            return layout;
        }
        catch (RuntimeException e)
        {
            throw new ServiceException("业务块布局JSON格式错误");
        }
    }

    public void validateLayout(String layoutJson, List<GenTable> tables)
    {
        validateLayout(layoutJson, tables, "frontend");
    }

    public void validateLayout(String layoutJson, List<GenTable> tables, String surface)
    {
        PageBusinessBlockLayout layout = parseLayout(layoutJson);
        if (!BUSINESS_BLOCK_CANVAS.equals(layout.getCanvas()))
        {
            return;
        }
        for (BusinessBlockInstance instance : layout.getBlocks())
        {
            if (instance == null)
            {
                continue;
            }
            BusinessBlockDefinition definition = findDefinition(instance.getBlockCode());
            validateInstanceConfig(definition, instance, tables, surface);
        }
    }

    public Map<String, List<BusinessBlockInstance>> parseEmbeddedBlockZones(String layoutJson)
    {
        Map<String, List<BusinessBlockInstance>> zones = new LinkedHashMap<String, List<BusinessBlockInstance>>();
        zones.put("top", new ArrayList<BusinessBlockInstance>());
        zones.put("bottom", new ArrayList<BusinessBlockInstance>());
        if (StringUtils.isEmpty(layoutJson))
        {
            return zones;
        }
        try
        {
            @SuppressWarnings("unchecked")
            Map<String, Object> layout = JSON.parseObject(layoutJson, Map.class);
            if (layout == null)
            {
                return zones;
            }
            Object embeddedBlocks = layout.get("embeddedBlocks");
            if (!(embeddedBlocks instanceof Map))
            {
                return zones;
            }
            @SuppressWarnings("unchecked")
            Map<Object, Object> embeddedMap = (Map<Object, Object>) embeddedBlocks;
            for (Map.Entry<Object, Object> entry : embeddedMap.entrySet())
            {
                String zone = String.valueOf(entry.getKey());
                zones.put(zone, parseBlockList(entry.getValue()));
            }
            return zones;
        }
        catch (RuntimeException e)
        {
            throw new ServiceException("嵌入业务块JSON格式错误");
        }
    }

    public void validateEmbeddedBlocks(String layoutJson, List<GenTable> tables)
    {
        validateEmbeddedBlocks(layoutJson, tables, "frontend");
    }

    public void validateEmbeddedBlocks(String layoutJson, List<GenTable> tables, String surface)
    {
        Map<String, List<BusinessBlockInstance>> zones = parseEmbeddedBlockZones(layoutJson);
        for (List<BusinessBlockInstance> blocks : zones.values())
        {
            if (blocks == null)
            {
                continue;
            }
            for (BusinessBlockInstance instance : blocks)
            {
                if (instance == null || StringUtils.isEmpty(instance.getBlockCode()))
                {
                    continue;
                }
                BusinessBlockDefinition definition = findDefinition(instance.getBlockCode());
                if (!isEmbeddedDefinition(definition))
                {
                    throw new ServiceException("业务块只能放在业务块页面：" + instance.getBlockCode());
                }
                validateInstanceConfig(definition, instance, tables, surface);
            }
        }
    }

    public boolean isEmbeddedDefinition(BusinessBlockDefinition definition)
    {
        return supportsPlacement(definition, PLACEMENT_EMBEDDED);
    }

    public boolean supportsPlacement(BusinessBlockDefinition definition, String placement)
    {
        return definition != null && definition.getPlacements() != null
                && definition.getPlacements().contains(placement);
    }

    public void validateInstanceConfig(BusinessBlockDefinition definition, BusinessBlockInstance instance,
            List<GenTable> tables)
    {
        if (definition == null)
        {
            throw new ServiceException("未知业务块：" + (instance == null ? "" : instance.getBlockCode()));
        }
        Map<String, String> config = instance == null || instance.getConfig() == null
                ? Collections.<String, String>emptyMap() : instance.getConfig();
        Map<String, GenTable> tableMap = buildTableMap(tables);
        List<BusinessBlockConfigField> fields = definition.getConfigs() == null
                ? Collections.<BusinessBlockConfigField>emptyList() : definition.getConfigs();
        for (BusinessBlockConfigField field : fields)
        {
            String value = StringUtils.defaultString(config.get(field.getKey()));
            if (Boolean.TRUE.equals(field.getRequired()) && StringUtils.isEmpty(value))
            {
                throw new ServiceException("业务块配置缺失：" + field.getKey());
            }
            if (StringUtils.isEmpty(value))
            {
                continue;
            }
            if ("table".equals(field.getType()))
            {
                requireTable(tableMap, value);
            }
            if ("field".equals(field.getType()))
            {
                String tableName = config.get(field.getTable());
                GenTable table = requireTable(tableMap, tableName);
                requireField(table, value);
            }
        }
    }

    public void validateInstanceConfig(BusinessBlockDefinition definition, BusinessBlockInstance instance,
            List<GenTable> tables, String surface)
    {
        String normalizedSurface = "admin".equals(surface) ? "admin" : "frontend";
        if (definition == null || definition.getSurfaces() == null
                || !definition.getSurfaces().contains(normalizedSurface))
        {
            String name = definition == null
                    ? StringUtils.defaultString(instance == null ? null : instance.getBlockCode())
                    : StringUtils.defaultIfEmpty(definition.getName(), definition.getCode());
            String allowedSurface = definition != null && definition.getSurfaces() != null
                    && definition.getSurfaces().contains("admin") ? "后台" : "前台";
            throw new ServiceException("业务块“" + name + "”仅支持" + allowedSurface + "页面");
        }
        if ("chart".equals(definition.getKind()))
        {
            chartValidator().validate(definition, instance, tables);
            return;
        }
        validateInstanceConfig(definition, instance, tables);
    }

    private ChartDatasetValidator chartValidator()
    {
        if (chartDatasetValidator == null)
        {
            chartDatasetValidator = new ChartDatasetValidator();
        }
        return chartDatasetValidator;
    }

    private List<BusinessBlockDefinition> loadDefinitions()
    {
        try
        {
            Resource[] resources = new PathMatchingResourcePatternResolver()
                    .getResources("classpath*:business-blocks/*/block.json");
            List<BusinessBlockDefinition> definitions = new ArrayList<BusinessBlockDefinition>();
            for (Resource resource : resources)
            {
                BusinessBlockDefinition definition = JSON.parseObject(readResource(resource), BusinessBlockDefinition.class);
                if (definition != null && StringUtils.isNotEmpty(definition.getCode()))
                {
                    definitions.add(normalizeDefinition(definition));
                }
            }
            Collections.sort(definitions, new Comparator<BusinessBlockDefinition>()
            {
                @Override
                public int compare(BusinessBlockDefinition left, BusinessBlockDefinition right)
                {
                    return StringUtils.defaultString(left.getCode()).compareTo(StringUtils.defaultString(right.getCode()));
                }
            });
            return Collections.unmodifiableList(definitions);
        }
        catch (IOException e)
        {
            throw new ServiceException("读取业务块定义失败");
        }
    }

    private BusinessBlockDefinition normalizeDefinition(BusinessBlockDefinition definition)
    {
        if (StringUtils.isEmpty(definition.getKind()))
        {
            definition.setKind("business");
        }
        if (definition.getSurfaces() == null || definition.getSurfaces().isEmpty())
        {
            definition.setSurfaces(Collections.singletonList("frontend"));
        }
        if (definition.getPlacements() == null || definition.getPlacements().isEmpty())
        {
            String placement = StringUtils.defaultIfEmpty(definition.getPlacement(), PLACEMENT_PAGE);
            definition.setPlacements(Collections.singletonList(placement));
        }
        if (definition.getDefaultSpan() == null)
        {
            definition.setDefaultSpan(Integer.valueOf(12));
        }
        return definition;
    }

    private List<BusinessBlockInstance> parseBlockList(Object value)
    {
        if (!(value instanceof List))
        {
            return new ArrayList<BusinessBlockInstance>();
        }
        return JSON.parseArray(JSON.toJSONString(value), BusinessBlockInstance.class);
    }

    private String readResource(Resource resource) throws IOException
    {
        InputStream inputStream = resource.getInputStream();
        try
        {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int length;
            while ((length = inputStream.read(buffer)) != -1)
            {
                outputStream.write(buffer, 0, length);
            }
            return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
        }
        finally
        {
            inputStream.close();
        }
    }

    private Map<String, GenTable> buildTableMap(List<GenTable> tables)
    {
        Map<String, GenTable> tableMap = new LinkedHashMap<String, GenTable>();
        if (tables == null)
        {
            return tableMap;
        }
        for (GenTable table : tables)
        {
            if (table != null && StringUtils.isNotEmpty(table.getTableName()))
            {
                tableMap.put(table.getTableName(), table);
            }
        }
        return tableMap;
    }

    private GenTable requireTable(Map<String, GenTable> tableMap, String tableName)
    {
        if (StringUtils.isEmpty(tableName) || !tableMap.containsKey(tableName))
        {
            throw new ServiceException("业务块表不存在：" + StringUtils.defaultString(tableName));
        }
        return tableMap.get(tableName);
    }

    private void requireField(GenTable table, String fieldName)
    {
        List<GenTableColumn> columns = table.getColumns() == null
                ? Collections.<GenTableColumn>emptyList() : table.getColumns();
        for (GenTableColumn column : columns)
        {
            if (column == null)
            {
                continue;
            }
            if (StringUtils.equals(fieldName, column.getColumnName()) || StringUtils.equals(fieldName, column.getJavaField()))
            {
                return;
            }
        }
        throw new ServiceException("业务块字段不存在：" + table.getTableName() + "." + fieldName);
    }
}
