package com.zzwtec.wechat.rpc.inject;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.kit.StrKit;
import com.zzwtec.wechat.common.ErrorMsg;
import com.zzwtec.wechat.config.ServiceConfig;
import com.zzwtec.wechat.config.WeChatConfig;
import com.zzwtec.wechat.rpc.APIResponse;
import com.zzwtec.wechat.rpc.annotation.ZZWApiAction;
import com.zzwtec.wechat.util.HttpUtil;
import com.zzwtec.wechat.util.security.EncoderHandler;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

/**
 *@author 毛文超
 * */
public class ProxyBuilder {

    private static final Map<String,Object> PROXY_MAP = new HashMap<>(10);

    @SuppressWarnings("unchecked")
    public static <T> T  build(Class<?> target, ServiceConfig serviceConfig){

        String name = target.getName();
        String serviceName = serviceConfig.getServiceName();
        Object proxyObject = PROXY_MAP.get(name + ":" + serviceName);
        if(proxyObject != null){
            return (T)proxyObject;
        }

        Map<String, String[]> methodMaps = AbstractMethodUtils.getMethodMaps(target);

        Object proxyInstance = Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, (o, method, objects) -> {
            //对default方法做特殊处理
            if (method.isDefault()) {
                Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                        .getDeclaredConstructor(Class.class, int.class);
                constructor.setAccessible(true);

                Class<?> declaringClass = method.getDeclaringClass();
                int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;

                return constructor.newInstance(declaringClass, allModes)
                        .unreflectSpecial(method, declaringClass)
                        .bindTo(o)
                        .invokeWithArguments(objects);
            }

            //非默认方法
            ZZWApiAction zzwApiAction = method.getAnnotation(ZZWApiAction.class);
            if (zzwApiAction == null) {
                throw new RuntimeException("作为服务接口的方法必须使用ZZWApiAction注解修饰");
            }
            //返回值类型
            Class<?> returnType = method.getReturnType();
            //如果返回值类型不对就抛出异常
            if (returnType != APIResponse.class) {
                throw new RuntimeException("作为服务接口的方法返回值必须是APIResponse!");
            }

            //开始进入调用逻辑

            //获取服务接口
            String url = zzwApiAction.value();

            //组装参数
            String[] paraNames = methodMaps.get(method.toString());
            JSONObject data = AbstractApiServiceUtils.buildData(url,paraNames, objects);

            return AbstractApiServiceUtils.post(serviceConfig,url, data);
        });

        PROXY_MAP.put(target.getName() + ":" + serviceName,proxyInstance);

        return (T)proxyInstance;
    }


    /**
     * 方法反射工具类,用于获取方法的参数名称
     * */
    abstract static class AbstractMethodUtils {

        static Map<String,String[]> getMethodMaps(Class<?> clazz){
            Method[] methods = clazz.getMethods();
            Map<String,String[]> result = new HashMap<>(methods.length);
            for (Method method : methods) {
                Parameter[] parameters = method.getParameters();
                String[] parameterNames = new String[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    parameterNames[i] = parameter.getName();
                }
                result.put(method.toString(),parameterNames);
            }
            return result;
        }

    }

    abstract static class AbstractApiServiceUtils {
        private static final Logger logger = LoggerFactory.getLogger(AbstractApiServiceUtils.class);
        private static String getRequestId() {
            return String.format("%s-%d", "zzwtec-wechat", System.currentTimeMillis());
        }

        static JSONObject buildData(String url,String[] paraNames, Object... args){
            JSONObject requestJSON = new JSONObject();
            requestJSON.put("requestId",getRequestId());
            int indexOf = url.indexOf("$");
            String substring = url.substring(0, indexOf);
            //如果参数只有一个或者没有参数
            if(args == null ||  args.length <= 1){
                //没有参数直接返回
                if(args == null || args.length == 0){
                    return requestJSON;
                }
                if(!StringUtils.equalsIgnoreCase(substring,"other")){
                    requestJSON.put("data",args[0]);
                    return requestJSON;
                }
            }

            JSONObject dataJson = new JSONObject();
            for (int i = 0; i < args.length; i++) {
                dataJson.put(paraNames[i],args[i]);
            }

            if(StringUtils.equalsIgnoreCase(substring,"api")){
                requestJSON.putAll(dataJson);
            }else{
                requestJSON.put("data",dataJson);
            }
            return requestJSON;
        }

        public static APIResponse post(ServiceConfig serviceConfig, String path, JSONObject postJson) {
            String json = postJson.toJSONString();
            //开始拼装服务的url
            int indexOf = path.indexOf("$");
            String baseUrlKey = path.substring(0, indexOf);
            String servicePath = path.substring(indexOf + 1, path.length());
            String serviceUrl = createURL(serviceConfig,baseUrlKey,servicePath);
            if(StrKit.isBlank(serviceUrl)){
                return new APIResponse(1, ErrorMsg._1);
            }

            Map<String, String> headers = createHeaders(json);
            String resultJson = HttpUtil.postJSON(serviceUrl, headers, json);
            JSONObject jsonObject = JSON.parseObject(resultJson);
            // 验证响应状态
            if (jsonObject.containsKey("status") && jsonObject.getInteger("status") != 200) {
                logger.warn(MessageFormat.format("\n value:{0} \n request:{1} \n response:{2}", serviceUrl, postJson, resultJson));
                return new APIResponse(1, ErrorMsg._1);
            }

            APIResponse response;
            if(jsonObject.containsKey("error")){
                response = new APIResponse(jsonObject.getIntValue("error"), jsonObject.getString("errormsg"));
            }else{
                response = new APIResponse(jsonObject.getIntValue("code"), jsonObject.getString("msg"), jsonObject.getString("data"));
            }
            // 转换执行结果
            // 执行成功则转换msg，执行失败则记录结果
            if (response.getCode() == 0) {
                response.setMsg(ErrorMsg._0);
            } else {
                if (response.getCode() == 1) {
                    response.setMsg(ErrorMsg._1);
                }
            }
            logger.info(MessageFormat.format("\n value:{0} \n request:{1} \n response:{2}", serviceUrl, postJson, resultJson));
            return response;
        }

        static String createURL(ServiceConfig serviceConfig, String baseServiceKey, String path) {
            return String.format("%s/%s", serviceConfig.getBaseServiceUrl(baseServiceKey), path);
        }

        static Map<String, String> createHeaders(String postData) {
            Map<String, String> headers = new HashMap<>();
            headers.put("zzw-sign", createSign(postData));
            return headers;
        }

        /**
         * 入参序列化成json格式
         * str1 = {"requestId":"xxx","data":{"id":"11","name":"z3"}}
         * str2 = str1 + key
         * str3 = md5/sha1/sha512(str2).toUpperCase()
         * sign = str3
         */
        static String createSign(String str1) {
            String appid = WeChatConfig.weChatConfig.getAppId();
            String secretKey = WeChatConfig.weChatConfig.getZzwtecSecretKey();
            String str2 = str1 + secretKey;
            String str3 = EncoderHandler.MD5(str2).toUpperCase();
            return String.format("appid=%s;sign=%s", appid, str3);
        }


    }

}
