package cn.myapps.runtime.common.controller;

import cn.hutool.http.HttpStatus;
import cn.myapps.common.controller.Resource;
import cn.myapps.common.controller.ResourceNotFoundException;
import cn.myapps.common.data.DataPackage;
import cn.myapps.common.data.ParamsTable;
import cn.myapps.common.model.api.ApiConfig;
import cn.myapps.designtime.api.service.ApiDesignTimeService;
import cn.myapps.designtime.common.cache.DesignTimeSerializableCache;
import cn.myapps.designtime.common.service.DesignTimeServiceManager;
import cn.myapps.designtime.storages.DesignTimeObjectWrapper;
import cn.myapps.runtime.dynaform.document.ejb.Document;
import cn.myapps.runtime.dynaform.form.ejb.ValidateMessage;
import cn.myapps.runtime.macro.runner.IRunner;
import cn.myapps.runtime.macro.runner.JavaScriptFactory;
import com.bcxin.saas.core.components.JsonProvider;
import com.bcxin.saas.core.exceptions.SaasBadException;
import com.bcxin.saas.core.utils.SqlUtil;
import de.odysseus.staxon.json.JsonXMLConfig;
import de.odysseus.staxon.json.JsonXMLConfigBuilder;
import de.odysseus.staxon.json.JsonXMLInputFactory;
import de.odysseus.staxon.xml.util.PrettyXMLEventWriter;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.mozilla.javascript.Undefined;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import java.io.*;
import java.util.*;
import java.util.function.Supplier;

/**
 * MagicApi模块
 */
@Component
public class MagicApiController extends AbstractRuntimeController{
    private static Logger logger = LoggerFactory.getLogger(MagicApiController.class);

    private final JsonProvider jsonProvider;

    public MagicApiController(JsonProvider jsonProvider) {
        this.jsonProvider = jsonProvider;
    }

    /**
     *  自定义api
     * @param response
     */
    @RequestMapping("/magic-api/**")
    //@Cacheable
    //todo
    public void execute(@RequestBody(required = false) String content, HttpServletResponse response) {
        content = SqlUtil.encode(content);
        StringBuilder sb = new StringBuilder();
        try {
            ParamsTable params = getParams();
            String requestURI = request.getRequestURI();
            String contextPath = request.getContextPath();
            String apiUrl = requestURI.replace(contextPath + "/magic-api", "");

            ApiConfig apiConfig = null;
            /**
             * 获取配置信息
             */
            Supplier<ApiConfig> extractMatchedApiConfig = () -> {
                try {
                    ApiDesignTimeService service = DesignTimeServiceManager.apiDesignTimeService();
                    DataPackage<ApiConfig> apiConfigs = service.query("", "", 1, Integer.MAX_VALUE);
                    PathMatcher matcher = new AntPathMatcher();

                    for (Iterator<ApiConfig> iterator = apiConfigs.getDatas().iterator(); iterator.hasNext(); ) {
                        ApiConfig api = iterator.next();
                        String url = api.getRequestUrl().replaceAll("\\{[^\\}]+\\}", "**");
                        if (matcher.match(url, apiUrl) && count(apiUrl, "/") == count(url, "/")) {
                            return api;
                        }
                    }

                    sb.append(String.format("====================================end============================================="));
                    logger.error(sb.toString());
                } catch (Exception ex) {
                    throw new SaasBadException(String.format("匹配MagicAPi=%s发生异常", apiUrl), ex);
                }

                return null;
            };

            if (DesignTimeSerializableCache.isReady()) {
                DesignTimeObjectWrapper<ApiConfig> modelWrapper = DesignTimeSerializableCache.getDesignModelWrapper(
                        DesignTimeSerializableCache.getSecondWrapperKey(apiUrl),
                        () -> {
                            return DesignTimeObjectWrapper.create(extractMatchedApiConfig.get());
                        }, Integer.MAX_VALUE, false);
                if (modelWrapper != null) {
                    apiConfig = (ApiConfig) modelWrapper.getInstance();
                }
            } else {
                apiConfig = extractMatchedApiConfig.get();
            }

            if (apiConfig == null) {
                throw new ResourceNotFoundException("Api Not Found");
            }

            String applicationId = apiConfig.getApplicationid();
            setParams(apiConfig.getRequestUrl(), apiUrl, params);
            params.setParameter("application", applicationId);

            params.setParameter("_content", content);
            IRunner runner = JavaScriptFactory.getInstance(session.getId(), applicationId);
            runner.initBSFManager(new Document(), params, getUser(), new ArrayList<ValidateMessage>());
            String apiLabel = "Api(" + apiConfig.getId() + ")." + apiConfig.getName();
            if (!StringUtils.hasLength(apiConfig.getResponseScript())) {
                throw new ResourceNotFoundException(String.format("当前的API内容无效:%s", jsonProvider.getJson(apiConfig)));
            }
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");

            Object result = runner.run(apiConfig, apiLabel, apiConfig.getResponseScript());
            try {
                if (result != null && !(result instanceof Undefined)) {
                    if ("json".equals(apiConfig.getResponseType())) {
                        response.setCharacterEncoding("UTF-8");
                        response.setContentType("application/json; charset=utf-8");
                        String str = null;
                        if (result instanceof Collection || result instanceof JSONArray) {
                            JSONArray jsonArray = cn.myapps.util.json.JsonTmpUtil.fromObject(result);
                            str = jsonArray.toString();
                        } else if(result instanceof String){
                            str = (String) result;
                        }else {
                            JSONObject json = JSONObject.fromObject(result);
                            str = json.toString();
                        }

                        try (PrintWriter out = response.getWriter()) {
                            out.append(str);
                        }
                    } else if ("xml".equals(apiConfig.getResponseType())) {
                        response.setCharacterEncoding("UTF-8");
                        response.setContentType("application/xml; charset=utf-8");
                        JSONObject json = JSONObject.fromObject(result);
                        String xml = json2xml(json.toString());
                        String simpleName = result.getClass().getSimpleName();
                        xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><" + simpleName + ">" + xml + "</" + simpleName + ">";
                        try (PrintWriter out = response.getWriter()) {
                            out.append(xml);
                        }
                    } else if ("binary".equals(apiConfig.getResponseType())) {
                        response.setContentType("application/x-download; charset=utf-8");
                        File file = (File) result;
                        response.setHeader("Content-Disposition", "attachment;filename=\"" + java.net.URLEncoder.encode(file.getName(), "utf-8") + "\"");
                        InputStream inputStream = new FileInputStream(file);
                        ServletOutputStream fileOutputStream = response.getOutputStream();
                        byte[] bytes = new byte[1024 * 1024];
                        int length = 0;
                        while ((length = inputStream.read(bytes)) != -1) {
                            fileOutputStream.write(bytes, 0, length);
                        }
                        fileOutputStream.write(bytes, 0, length);
                        fileOutputStream.close();
                    } else {
                        try (PrintWriter out = response.getWriter()) {
                            out.append((String) result);
                        }
                    }
                } else {
                    Map<String, Object> bodyMap = new HashMap<>();
                    bodyMap.put("msg", "API返回结果为空");
                    bodyMap.put("result", result == null ? "NULL" : result);
                    bodyMap.put("getId", apiConfig.getId());
                    bodyMap.put("getPath", apiConfig.getPath());

                    try (PrintWriter out = response.getWriter()) {
                        out.append(jsonProvider.getJson(bodyMap));
                    }
                }
            }
            catch (Exception ex) {
                logger.error("{}-响应API(数据={})发生异常", request.getRequestURI(), result, ex);
                throw ex;
            }
        } catch (Exception e) {
            logger.error("API请求异常:{}-{}", request.getRequestURI(), sb, e);
            response.setStatus(HttpStatus.HTTP_BAD_REQUEST);
            JSONObject json = JSONObject.fromObject(
                    Resource.error(500, String.format("异常:%s", e.getMessage()), null));

            try (PrintWriter out = response.getWriter()) {
                out.append(json.toString());
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * json转xml
     * @param json
     * @return
     */
    private static String json2xml(String json) {
        StringReader input = new StringReader(json);
        StringWriter output = new StringWriter();
        JsonXMLConfig config = new JsonXMLConfigBuilder().multiplePI(false).repairingNamespaces(false).build();
        try {
            XMLEventReader reader = new JsonXMLInputFactory(config).createXMLEventReader(input);
            XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(output);
            writer = new PrettyXMLEventWriter(writer);
            writer.add(reader);
            reader.close();
            writer.close();
        } catch (Exception e) {
            logger.error("json2xml 发生异常:{}", json, e);
        } finally {
            try {
                if (output != null) {
                    output.close();
                }

                if (input != null) {
                    input.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (output.toString().length() >= 38) {//remove <?xml version="1.0" encoding="UTF-8"?>
            return output.toString().substring(39);
        }
        return output.toString();
    }

    /**
     * 统计字符出现次数
     * @param content
     * @param target
     * @return
     */
    private static int count(String content, String target) {
        int count = 0;
        int index = 0;
        try {
            while (true) {
                index = content.indexOf(target, index + 1);
                if (index > 0) {
                    count++;
                } else {
                    break;
                }
            }
        } catch (Exception e) {
            logger.error("count 发生异常:content={}, target={}", content, target, e);
        }

        return count;
    }

    /**
     * 设置参数
     * @param before
     * @param latest
     * @param params
     */
    private void setParams(String before, String latest, ParamsTable params){// 如果两个字符串相同，那么就不需要进行对比了
        if (!before.equals(latest)) {
            String[] s1 = before.split("/");
            String[] s2 = latest.split("/");
            for(int i=0; i<s1.length; i++){
                String key = s1[i];
                String val = s2[i];
                if(!key.equals(val)){
                    params.setParameter(key.substring(1, key.length()-1), val);
                }
            }
        }
    }
}
