package com.topsunit.query.binding;

import com.huigou.cache.DictUtil;
import com.huigou.data.domain.query.QueryAbstractRequest;
import com.huigou.data.domain.query.QueryPageRequest;
import com.huigou.data.query.model.QueryDescriptor;
import com.huigou.data.query.model.QueryModel;
import com.huigou.util.ClassHelper;
import com.huigou.util.Constants;
import com.topsunit.query.annotations.Dictionary;
import com.topsunit.query.annotations.Param;
import com.topsunit.query.annotations.ResultType;
import net.sf.cglib.beans.BeanGenerator;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用于执行<sql-query>标签中的语句。
 *
 * @author yonghuan
 * @since 1.1.3
 */
final class SqlQueryInvokeStrategy implements InvokeStrategy {

    @Override
    public boolean supports(InvokeContext invokeContext, Method method, Object[] args) {
        return true;
    }

    @Override
    public Object invoke(InvokeContext invokeContext, Method method, Object[] args) {
        List<QueryAbstractRequest> requests = ArrayUtils.isEmpty(args)
                ? new ArrayList<>(1)
                : Arrays.stream(args).filter(arg -> arg instanceof QueryAbstractRequest)
                .map(arg -> (QueryAbstractRequest) arg)
                .collect(Collectors.toList());
        if (requests.size() > 1) {
            throw new IllegalArgumentException("只能设置一个自定QueryRequest");
        }
        if (requests.isEmpty()) {
            requests.add(generateQueryProxy(method, args));
        }

        QueryAbstractRequest query = requests.get(0);
        String queryName = method.getName();
        QueryDescriptor queryDescriptor = invokeContext.getSqlExecutor().getQuery(invokeContext.getXml(), queryName);
        QueryModel model = invokeContext.getSqlExecutor().getQueryModel(queryDescriptor, query);
        // 解析字典配置
        for (Dictionary dictionary : method.getAnnotationsByType(Dictionary.class)) {
            if (StringUtils.isNotEmpty(dictionary.value()) && StringUtils.isNotEmpty(dictionary.field())) {
                model.putDictionary(dictionary.field(), DictUtil.getDictionary(dictionary.value()));
            }
        }
        final QueryPageRequest pageModel = query.getPageModel();
        final boolean needPage = pageModel != null
                && pageModel.getPageIndex() != null
                && pageModel.getPageSize() != null;
        Map<String, Object> result = needPage ? invokeContext.getSqlExecutor().executeSlicedQuery(model)
                : invokeContext.getSqlExecutor().executeQuery(model);
        Class<?> retType = method.getReturnType();
        if (Map.class.isAssignableFrom(retType)) {
            return result;
        }
        if (Collection.class.isAssignableFrom(retType)) {
            List<Map<String, Object>> rows = (List<Map<String, Object>>) result.get(Constants.ROWS);
            ResultType resultType = method.getAnnotation(ResultType.class);
            return resultType == null
                    ? rows
                    : rows.stream().map(row -> ClassHelper.fromMap(resultType.value(), row)).collect(Collectors.toList());
        }
        if (Page.class.isAssignableFrom(retType)) {
            List<?> rows = (List<?>) result.get(Constants.ROWS);
            long total = ((Number) result.getOrDefault(Constants.RECORD, rows.size())).longValue();
            return needPage
                    ? new PageImpl(rows, new PageRequest(pageModel.getPageIndex() - 1, pageModel.getPageSize()), total)
                    : new PageImpl(rows);
        }
        throw new IllegalArgumentException(String.format("无法转换查询结果集为：%s", retType.getName()));
    }

    private QueryAbstractRequest generateQueryProxy(Method method, Object[] args) {
        BeanGenerator queryGenerator = new BeanGenerator();
        queryGenerator.setSuperclass(QueryAbstractRequest.class);
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            Param paramAnnotation = parameter.getAnnotation(Param.class);
            if (paramAnnotation != null) {
                Class parameterType = parameter.getType();
                if (Collection.class.isAssignableFrom(parameterType)) {
                    parameterType = String.class;
                }
                queryGenerator.addProperty(paramAnnotation.value(), parameterType);
            }
        }
        QueryAbstractRequest queryProxy = (QueryAbstractRequest) queryGenerator.create();
        PropertyDescriptor[] propertyDescriptors;
        try {
            propertyDescriptors = Introspector.getBeanInfo(queryProxy.getClass()).getPropertyDescriptors();
        } catch (IntrospectionException ie) {
            throw new IllegalArgumentException(ie);
        }
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Param paramAnnotation = parameter.getAnnotation(Param.class);
            if (paramAnnotation != null) {
                String paramName = paramAnnotation.value();
                PropertyDescriptor propertyDescriptor = Stream.of(propertyDescriptors)
                        .filter(pd -> pd.getName().equals(paramName))
                        .findAny()
                        .orElseThrow(() -> new IllegalArgumentException(String.format("无法匹配查询参数 %s", paramName)));
                Object paramVal = args[i];
                if (paramVal instanceof Collection) {
                    Collection<?> items = (Collection<?>) paramVal;
                    paramVal = items.stream().map(String::valueOf).collect(Collectors.joining(","));
                }
                try {
                    propertyDescriptor.getWriteMethod().invoke(queryProxy, paramVal);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new IllegalArgumentException(String.format("绑定参数 paramName=%s，paramValue=%s 时候出错", paramName, paramVal), e);
                }
            }
        }
        return queryProxy;
    }
}
