/*
 * Copyright 1999-2025 Alibaba Group Holding Ltd.
 *
 * Licensed 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 com.alibaba.nacos.plugin.datasource.mapper;

import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.ext.WhereBuilder;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import java.util.List;
import java.util.Map;

/**
 * The mapper of ai_resource.
 *
 * @author nacos
 * @since 3.2.0
 */
public interface AiResourceMapper extends Mapper {
    
    String QUERY_CONDITION_OR_GROUP = "query_condition_or_group";
    
    String QUERY_CONDITION_ALWAYS_EMPTY = "query_condition_always_empty";

    /**
     * Query count rows for ai_resource list.
     *
     * <p>Filters: namespace_id (required), type (optional), name (optional like), biz_tags (optional like).</p>
     */
    default MapperResult findAiResourceCountRows(MapperContext context) {
        WhereBuilder where = new WhereBuilder("SELECT count(*) FROM ai_resource");
        where.eq("namespace_id", context.getWhereParameter(FieldConstant.NAMESPACE_ID));

        appendExtraQueryCondition(where, context);

        return where.build();
    }

    /**
     * Query fetch rows for ai_resource list.
     */
    MapperResult findAiResourceFetchRows(MapperContext context);

    /**
     * Resolve the ORDER BY clause based on the orderBy parameter in the context. Only whitelisted values are accepted to
     * prevent SQL injection.
     *
     * @param context mapper context that may contain an {@link FieldConstant#ORDER_BY} parameter
     * @return SQL ORDER BY clause, e.g. {@code " ORDER BY download_count DESC"} or {@code " ORDER BY gmt_modified DESC"}
     */
    default String resolveOrderByClause(MapperContext context) {
        Object orderBy = context.getWhereParameter(FieldConstant.ORDER_BY);
        if (orderBy != null && FieldConstant.ORDER_BY_DOWNLOAD_COUNT.equals(String.valueOf(orderBy))) {
            return " ORDER BY download_count DESC";
        }
        return " ORDER BY gmt_modified DESC";
    }
    
    /**
     * Append additional query condition generated by upper-level converter.
     */
    @SuppressWarnings("unchecked")
    default void appendExtraQueryCondition(WhereBuilder where, MapperContext context) {
        Object alwaysEmptyObj = context.getWhereParameter(QUERY_CONDITION_ALWAYS_EMPTY);
        boolean alwaysEmpty = Boolean.TRUE.equals(alwaysEmptyObj);
        if (alwaysEmpty) {
            where.and().eq("1", 0);
            return;
        }
        appendAndConditions(where, context);
        Map<String, Object> orMap = castToMap(context.getWhereParameter(QUERY_CONDITION_OR_GROUP));
        if (orMap == null || orMap.isEmpty()) {
            return;
        }
        if (orMap.size() == 1) {
            Map.Entry<String, Object> only = orMap.entrySet().iterator().next();
            appendSingleAndCondition(where, only.getKey(), only.getValue(), false);
            return;
        }
        appendOrConditions(where, orMap);
    }
    
    /**
     * Append standard AND conditions from mapper context.
     */
    default void appendAndConditions(WhereBuilder where, MapperContext context) {
        appendSingleAndCondition(where, "name", context.getWhereParameter(FieldConstant.NAME), true);
        appendSingleAndCondition(where, "biz_tags", context.getWhereParameter(FieldConstant.BIZ_TAGS), true);
        appendSingleAndCondition(where, "type", context.getWhereParameter(FieldConstant.TYPE), false);
        appendSingleAndCondition(where, "scope", context.getWhereParameter(FieldConstant.SCOPE), false);
        appendSingleAndCondition(where, "owner", context.getWhereParameter(FieldConstant.OWNER), false);
    }
    
    /**
     * Append one AND condition. List value is treated as IN.
     */
    default void appendSingleAndCondition(WhereBuilder where, String field, Object value, boolean likeMatch) {
        if (StringUtils.isBlank(field) || value == null) {
            return;
        }
        if (value instanceof List) {
            if (((List<?>) value).isEmpty()) {
                return;
            }
            where.and().in(field, ((List<?>) value).toArray());
            return;
        }
        if (likeMatch) {
            where.and().like(field, value);
        } else {
            where.and().eq(field, value);
        }
    }
    
    /**
     * Append OR condition group with parentheses.
     */
    default void appendOrConditions(WhereBuilder where, Map<String, Object> orMap) {
        where.and().startParentheses();
        boolean appended = false;
        for (Map.Entry<String, Object> each : orMap.entrySet()) {
            String field = each.getKey();
            Object value = each.getValue();
            if (StringUtils.isBlank(field) || value == null) {
                continue;
            }
            if (appended) {
                where.or();
            }
            if (value instanceof List) {
                if (((List<?>) value).isEmpty()) {
                    continue;
                }
                where.in(field, ((List<?>) value).toArray());
            } else {
                where.eq(field, value);
            }
            appended = true;
        }
        if (!appended) {
            where.eq("1", 0);
        }
        where.endParentheses();
    }
    
    /**
     * Cast raw mapper context value to map with string keys.
     */
    @SuppressWarnings("unchecked")
    default Map<String, Object> castToMap(Object value) {
        if (!(value instanceof Map)) {
            return null;
        }
        Map<Object, Object> raw = (Map<Object, Object>) value;
        Map<String, Object> result = new java.util.LinkedHashMap<>();
        for (Map.Entry<Object, Object> each : raw.entrySet()) {
            result.put(each.getKey() == null ? null : String.valueOf(each.getKey()), each.getValue());
        }
        return result;
    }

    @Override
    default String getTableName() {
        return TableConstant.AI_RESOURCE;
    }
}

