package com.cloudcc.core;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

/**
 * OpenAPI universal call client ({@code /openApi/common}), collection of
 * publicly visible methods and
 * {@code docs/CCService_SDK_PublicDoc.md} alignment; methods that are not
 * implemented or return {@code null} indicate they are still pending alignment
 * with gateway parameters.
 * <p>
 * Query semantics: {@link #cquery(String, String)} is the "cquery" two
 * parameters; sorting uses {@link #cquery(String, String, String)};
 * for specific fields use {@link #cqueryByFields(String, String, String)}; when
 * permissions are included and the third parameter is a field list use
 * {@link #cqueryWithRoleRightByFields(String, String, String)} (equivalent to
 * the four-parameter overload with isAddDelete=false).
 */
public class CCService {

    // -------- Service name constants --------
    public static final String SVC_CQUERY = "cquery";
    public static final String SVC_CQUERY_WITH_ROLE_RIGHT = "cqueryWithRoleRight";
    public static final String SVC_PAGE_QUERY = "pageQuery";
    public static final String SVC_PAGE_QUERY_WITH_ROLE_RIGHT = "pageQueryWithRoleRight";
    public static final String SVC_CQL_QUERY_WITH_LOG_INFO = "cqlQueryWithLogInfo";
    public static final String SVC_INSERT = "insert";
    public static final String SVC_UPDATE = "update";
    public static final String SVC_DELETE = "delete";
    public static final String SVC_UPSERT = "upsert";
    public static final String SVC_INSERT_WITH_ROLE_RIGHT = "insertWithRoleRight";
    public static final String SVC_UPDATE_WITH_ROLE_RIGHT = "updateWithRoleRight";
    public static final String SVC_DELETE_WITH_ROLE_RIGHT = "deleteWithRoleRight";
    public static final String SVC_UPSERT_WITH_ROLE_RIGHT = "upsertWithRoleRight";
    /** See public documentation cqueryNoLang */
    public static final String SVC_CQUERY_NO_LANG = "cqueryNoLang";
    /** See public documentation pagedQuery (String page number) */
    public static final String SVC_PAGED_QUERY = "pagedQuery";
    /** See public documentation pagedQueryWithRoleRight */
    public static final String SVC_PAGED_QUERY_WITH_ROLE_RIGHT = "pagedQueryWithRoleRight";
    /** See public documentation getTotalRecordSize */
    public static final String SVC_GET_TOTAL_RECORD_SIZE = "getTotalRecordSize";
    /** See public documentation getTotalRecordSizeWithRoleRight */
    public static final String SVC_GET_TOTAL_RECORD_SIZE_WITH_ROLE_RIGHT = "getTotalRecordSizeWithRoleRight";
    /** CQL query only (multi-object join query) */
    public static final String SVC_CQL_QUERY = "cqlQuery";
    public static UserInfo userInfo;
    public String ACCESSTOKEN;
    public String path;
    public String URL;
    protected Map<String, Object> record_new;
    protected Map<String, Object> record_old;
    public Map<String, String> Config = Tool.executeGetTokenCommand();
    public ServiceResult trigger = new ServiceResult();
    public String objectApiName;

    public CCService(UserInfo userInfo) {
        this.ACCESSTOKEN = Config.get("accessToken");
        this.path = Config.get("apiSvc") + "/openApi/common";
    }

    // -------- invokeCommon --------

    public JSONObject invokeCommon(JSONObject parObj) {
        String relStr = CCPost(path, parObj.toString());
        return JSONObject.parseObject(relStr);
    }

    // -------- cquery --------

    @SuppressWarnings("rawtypes")
    public List<CCObject> cquery(String apiName, String expression) {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQUERY);
        parObj.put("objectApiName", apiName);
        parObj.put("expressions", expression);
        parObj.put("isAddDelete", "false");
        parObj.put("fields", "");

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(apiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cquery error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    /**
     * With order by clause, corresponding to the three-parameter overload of
     * "cquery" in the public documentation.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cquery(String apiName, String expression, String ordings) {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQUERY);
        parObj.put("objectApiName", apiName);
        parObj.put("expressions", expression);
        parObj.put("isAddDelete", "false");
        parObj.put("fields", "");
        if (ordings != null && !ordings.isEmpty()) {
            parObj.put("ordings", ordings);
        }

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(apiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cquery error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    /**
     * Specify return fields, corresponding to "cqueryByFields" in the public
     * documentation.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryByFields(String objectApiName, String expression, String fields) {
        return cqueryWithFieldList(objectApiName, expression, fields);
    }

    @SuppressWarnings("rawtypes")
    private List<CCObject> cqueryWithFieldList(String apiName, String expression, String fields) {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQUERY);
        parObj.put("objectApiName", apiName);
        parObj.put("expressions", expression);
        parObj.put("isAddDelete", "false");
        parObj.put("fields", fields == null ? "" : fields);

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(apiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cquery error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    /**
     * No translation of multilingual, corresponding to "cqueryNoLang" in the public
     * documentation.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryNoLang(String objectApiName, String expression, String fields, String ordings) {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQUERY_NO_LANG);
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", expression == null ? "1=1" : expression);
        parObj.put("fields", fields == null ? "" : fields);
        if (ordings != null && !ordings.isEmpty()) {
            parObj.put("ordings", ordings);
        }

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(objectApiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cqueryNoLang error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    // -------- cqueryWithRoleRight --------

    /**
     * Query with permissions, corresponding to overload 1 in the public
     * documentation.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryWithRoleRight(String apiName, String expression) throws Exception {
        return cqueryWithRoleRight(apiName, expression, "false", "");
    }

    /**
     * Corresponding to overload 2 in the public documentation: the third parameter
     * is isAddDeleteHttp.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryWithRoleRight(String apiName, String expression, String isAddDeleteHttp)
            throws Exception {
        return cqueryWithRoleRight(apiName, expression, isAddDeleteHttp, "");
    }

    /**
     * Corresponding to overload 3 in the public documentation: contains field list.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryWithRoleRight(String apiName, String expression, String isAddDeleteHttp,
            String fields) throws Exception {
        return cqueryWithRoleRight(apiName, expression, isAddDeleteHttp, fields, null, null);
    }

    /**
     * Corresponding to overload 4 in the public documentation: multiple currencies
     * and language codes.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryWithRoleRight(String apiName, String expression, String isAddDeleteHttp,
            String fields, String iscurrency, String islang) throws Exception {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQUERY_WITH_ROLE_RIGHT);
        parObj.put("objectApiName", apiName);
        parObj.put("expressions", expression);
        parObj.put("isAddDelete", isAddDeleteHttp == null ? "false" : isAddDeleteHttp);
        parObj.put("fields", fields == null ? "" : fields);
        if (iscurrency != null) {
            parObj.put("iscurrency", iscurrency);
        }
        if (islang != null) {
            parObj.put("islang", islang);
        }

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(apiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cqueryWithRoleRight error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    /**
     * Convenience: isAddDelete is fixed to false, the third parameter is only
     * fields (equivalent to the four-parameter version with false + fields).
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryWithRoleRightByFields(String objectApiName, String expression, String fields)
            throws Exception {
        return cqueryWithRoleRight(objectApiName, expression, "false", fields);
    }

    /**
     * Query with permissions without translating multiple languages, corresponding
     * to public documentation "cqueryWithRoleRightNoLang".
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqueryWithRoleRightNoLang(String objectApiName, String expression, String fields)
            throws Exception {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", "cqueryWithRoleRightNoLang");
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", expression);
        parObj.put("fields", fields == null ? "" : fields);

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(objectApiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cqueryWithRoleRightNoLang error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    // -------- pageQuery --------

    public PageQueryResult pageQuery(String apiName, String expression, String fields, int pageNum, int pageSize) {
        return pageQueryWithService(SVC_PAGE_QUERY, apiName, expression, fields, pageNum, pageSize);
    }

    /**
     * Paged query with permissions, corresponding to public documentation
     * "pagedQueryWithRoleRight" (OpenAPI input parameters are numeric page number
     * and pageSize).
     */
    public PageQueryResult pageQueryWithRoleRight(String apiName, String expression, String fields, int pageNum,
            int pageSize) {
        return pageQueryWithService(SVC_PAGE_QUERY_WITH_ROLE_RIGHT, apiName, expression, fields, pageNum, pageSize);
    }

    @SuppressWarnings("rawtypes")
    private PageQueryResult pageQueryWithService(String serviceName, String apiName, String expression, String fields,
            int pageNum, int pageSize) {
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", serviceName);
        parObj.put("objectApiName", apiName);
        parObj.put("expressions", expression);
        parObj.put("fields", fields == null ? "" : fields);
        parObj.put("pageNum", pageNum);
        parObj.put("pageSize", pageSize);

        JSONObject relObj = invokeCommon(parObj);
        List<CCObject> records = new ArrayList<CCObject>();
        if ("true".equals(relObj.getString("result"))) {
            Object rawData = relObj.get("data");
            JSONArray rows = null;
            if (rawData instanceof JSONObject) {
                rows = ((JSONObject) rawData).getJSONArray("records");
            } else if (rawData instanceof JSONArray) {
                rows = (JSONArray) rawData;
                // When the platform returns an array directly, supplement pagination metadata
                // to ensure PageQueryResult methods are available
                JSONObject synth = new JSONObject();
                synth.put("records", rows);
                synth.put("totalCount", rows.size());
                synth.put("pageNum", pageNum);
                synth.put("pageSize", pageSize);
                synth.put("pageCount", 1);
                synth.put("hasPre", false);
                synth.put("hasNext", false);
                relObj.put("data", synth);
            }
            if (rows != null) {
                List<Map> rst = rows.toJavaList(Map.class);
                for (Map m : rst) {
                    CCObject co = new CCObject(apiName);
                    for (Object _key : m.keySet()) {
                        co.put((String) _key, m.get(_key));
                    }
                    records.add(co);
                }
            }
        } else {
            System.err.println("pageQuery error: " + relObj.getString("returnInfo"));
        }
        return new PageQueryResult(records, relObj);
    }

    // -------- pagedQuery (String page number) / getTotalRecordSize /
    // pageQuery(Map) --------

    /**
     * Public documentation "pagedQuery" basic pagination, using different
     * serviceName from {@link #pageQuery(String, String, String, int, int)},
     * if the platform only implements one, both can be directed to the same
     * implementation on the deployment side.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQuery(String objectApiName, String expression, String pageNUM, String pageSize) {
        return pagedQuery(objectApiName, expression, pageNUM, pageSize, null, null, null);
    }

    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQuery(String objectApiName, String expression, String pageNUM, String pageSize,
            String isAddDelete) {
        return pagedQuery(objectApiName, expression, pageNUM, pageSize, isAddDelete, null, null);
    }

    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQuery(String objectApiName, String expression, String pageNUM, String pageSize,
            String isAddDelete, String fields) {
        return pagedQuery(objectApiName, expression, pageNUM, pageSize, isAddDelete, fields, null);
    }

    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQuery(String objectApiName, String expression, String pageNUM, String pageSize,
            String isAddDelete, String fields, String islang) {
        List<CCObject> records = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_PAGED_QUERY);
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", expression);
        parObj.put("pageNum", parsePageNum(pageNUM));
        parObj.put("pageSize", parsePageSize(pageSize));
        if (isAddDelete != null) {
            parObj.put("isAddDelete", isAddDelete);
        }
        if (fields != null) {
            parObj.put("fields", fields);
        }
        if (islang != null) {
            parObj.put("islang", islang);
        }

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            records.addAll(extractRecordsFromPageData(objectApiName, relObj));
        } else {
            System.err.println("pagedQuery error: " + relObj.getString("returnInfo"));
        }
        return records;
    }

    /**
     * Public documentation "pagedQueryWithRoleRight".
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQueryWithRoleRight(String objectApiName, String expression, String pageNUM,
            String pageSize) throws Exception {
        return pagedQueryWithRoleRight(objectApiName, expression, pageNUM, pageSize, null, null, null);
    }

    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQueryWithRoleRight(String objectApiName, String expression, String pageNUM,
            String pageSize, String isAddDelete) throws Exception {
        return pagedQueryWithRoleRight(objectApiName, expression, pageNUM, pageSize, isAddDelete, null, null);
    }

    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQueryWithRoleRight(String objectApiName, String expression, String pageNUM,
            String pageSize, String isAddDelete, String fields) throws Exception {
        return pagedQueryWithRoleRight(objectApiName, expression, pageNUM, pageSize, isAddDelete, fields, null);
    }

    @SuppressWarnings("rawtypes")
    public List<CCObject> pagedQueryWithRoleRight(String objectApiName, String expression, String pageNUM,
            String pageSize, String isAddDelete, String fields, String islang) throws Exception {
        List<CCObject> records = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_PAGED_QUERY_WITH_ROLE_RIGHT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", expression);
        parObj.put("pageNum", parsePageNum(pageNUM));
        parObj.put("pageSize", parsePageSize(pageSize));
        if (isAddDelete != null) {
            parObj.put("isAddDelete", isAddDelete);
        }
        if (fields != null) {
            parObj.put("fields", fields);
        }
        if (islang != null) {
            parObj.put("islang", islang);
        }

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            records.addAll(extractRecordsFromPageData(objectApiName, relObj));
        } else {
            System.err.println("pagedQueryWithRoleRight error: " + relObj.getString("returnInfo"));
        }
        return records;
    }

    /** Public documentation "getTotalRecordSize" basic version. */
    public long getTotalRecordSize(String objectApiName, String expression) {
        return getTotalRecordSize(objectApiName, expression, null);
    }

    /** Public documentation "getTotalRecordSize" with deleted mark. */
    public long getTotalRecordSize(String objectApiName, String expression, String isAddDelete) {
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_GET_TOTAL_RECORD_SIZE);
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", expression);
        if (isAddDelete != null) {
            parObj.put("isAddDelete", isAddDelete);
        }
        JSONObject relObj = invokeCommon(parObj);
        return parseTotalCount(relObj);
    }

    /** Public documentation "getTotalRecordSizeWithRoleRight". */
    public long getTotalRecordSizeWithRoleRight(String objectApiName, String expression) {
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_GET_TOTAL_RECORD_SIZE_WITH_ROLE_RIGHT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", expression);
        JSONObject relObj = invokeCommon(parObj);
        return parseTotalCount(relObj);
    }

    /**
     * Public documentation "pageQuery" simplified pagination (returns total +
     * list), coexisting with the {@link PageQueryResult} version of
     * {@link #pageQuery(String, String, String, int, int)}.
     */
    @SuppressWarnings("rawtypes")
    public Map<String, Object> pageQuery(String objectApiName, String expression, String pageNUM, String pageSize) {
        Map<String, Object> out = new HashMap<String, Object>();
        PageQueryResult pqr = pageQuery(objectApiName, expression, "", parsePageNum(pageNUM), parsePageSize(pageSize));
        out.put("total", pqr.getTotalCount());
        out.put("list", pqr.records);
        return out;
    }

    private static int parsePageNum(String pageNUM) {
        try {
            return Integer.parseInt(pageNUM == null ? "1" : pageNUM.trim());
        } catch (NumberFormatException e) {
            return 1;
        }
    }

    private static int parsePageSize(String pageSize) {
        try {
            return Integer.parseInt(pageSize == null ? "20" : pageSize.trim());
        } catch (NumberFormatException e) {
            return 20;
        }
    }

    private long parseTotalCount(JSONObject relObj) {
        if (relObj == null || !"true".equals(relObj.getString("result"))) {
            return 0L;
        }
        Object data = relObj.get("data");
        if (data == null) {
            return 0L;
        }
        if (data instanceof Number) {
            return ((Number) data).longValue();
        }
        if (data instanceof JSONObject) {
            JSONObject jo = (JSONObject) data;
            if (jo.containsKey("totalCount")) {
                return jo.getLongValue("totalCount");
            }
            if (jo.containsKey("total")) {
                return jo.getLongValue("total");
            }
        }
        try {
            return Long.parseLong(data.toString());
        } catch (Exception e) {
            return 0L;
        }
    }

    @SuppressWarnings("rawtypes")
    private List<CCObject> extractRecordsFromPageData(String objectApiName, JSONObject relObj) {
        List<CCObject> records = new ArrayList<CCObject>();
        Object rawData = relObj.get("data");
        JSONArray rows = null;
        if (rawData instanceof JSONObject) {
            rows = ((JSONObject) rawData).getJSONArray("records");
        } else if (rawData instanceof JSONArray) {
            rows = (JSONArray) rawData;
        }
        if (rows == null) {
            return records;
        }
        List<Map> rst = rows.toJavaList(Map.class);
        for (Map m : rst) {
            CCObject co = new CCObject(objectApiName);
            for (Object _key : m.keySet()) {
                co.put((String) _key, m.get(_key));
            }
            records.add(co);
        }
        return records;
    }

    // -------- cqlQuery / cqlQueryWithLogInfo / cqlQueryWithStatic --------

    @SuppressWarnings("rawtypes")
    public List<CCObject> cqlQuery(String apiName, String expression) {
        return cqlQueryWithLogInfo(apiName, expression, "");
    }

    /**
     * Multi-object joint query, corresponding to public documentation
     * "cqlQuery(String cql)" returns {@code List<Map>}.
     */
    @SuppressWarnings("rawtypes")
    public List<Map> cqlQuery(String cql) {
        List<Map> out = new ArrayList<Map>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQL_QUERY);
        parObj.put("expressions", cql);

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            JSONArray arr = relObj.getJSONArray("data");
            if (arr != null) {
                out.addAll(arr.toJavaList(Map.class));
            }
        } else {
            System.err.println("cqlQuery (joint query) error: " + relObj.getString("returnInfo"));
        }
        return out;
    }

    /**
     * Public documentation "cqlQueryThrowException": throws an exception on failure
     * to get platform error information.
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqlQueryThrowException(String objectApiName, String cql) throws Exception {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQL_QUERY_WITH_LOG_INFO);
        parObj.put("objectApiName", objectApiName);
        parObj.put("expressions", cql);

        JSONObject relObj = invokeCommon(parObj);
        if (!"true".equals(relObj.getString("result"))) {
            String info = relObj.getString("returnInfo");
            throw new Exception(info != null ? info : "cqlQuery failed");
        }
        List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
        for (Map m : rst) {
            CCObject co = new CCObject(objectApiName);
            for (Object _key : m.keySet()) {
                co.put((String) _key, m.get(_key));
            }
            orst.add(co);
        }
        return orst;
    }

    /**
     * The third parameter corresponds to logLevel in the public documentation
     * (DEBUG / INFO / ERROR).
     */
    @SuppressWarnings("rawtypes")
    public List<CCObject> cqlQueryWithLogInfo(String apiName, String expression, String logLevel) {
        List<CCObject> orst = new ArrayList<CCObject>();
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_CQL_QUERY_WITH_LOG_INFO);
        parObj.put("objectApiName", apiName);
        parObj.put("expressions", expression);
        if (logLevel != null && !logLevel.isEmpty()) {
            parObj.put("logLevel", logLevel);
        }

        JSONObject relObj = invokeCommon(parObj);
        if ("true".equals(relObj.getString("result"))) {
            List<Map> rst = relObj.getJSONArray("data").toJavaList(Map.class);
            for (Map m : rst) {
                CCObject co = new CCObject(apiName);
                for (Object _key : m.keySet()) {
                    co.put((String) _key, m.get(_key));
                }
                orst.add(co);
            }
        } else {
            System.err.println("cqlQueryWithLogInfo error: " + relObj.getString("returnInfo"));
        }
        return orst;
    }

    // -------- CRUD --------

    /**
     * Public documentation "update" standard version, returns
     * {@link ServiceResult}.
     */
    public ServiceResult update(CCObject cobj) {
        ServiceResult sr = new ServiceResult();
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);

        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_UPDATE);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            TriggerInvoker invoker = new TriggerInvoker(userInfo, "update", "before");
            invoker.handler(cobj);
            JSONObject relObj = invokeCommon(parObj);
            boolean result = Boolean.TRUE.equals(relObj.getBoolean("result"))
                    || "true".equals(relObj.getString("result"));
            if (result) {
                JSONObject outObj = relObj.getJSONObject("data");
                if (outObj != null && outObj.containsKey("ids")) {
                    JSONArray outData = outObj.getJSONArray("ids");
                    StringBuilder sb = new StringBuilder();
                    boolean hasSuccess = false;
                    for (int i = 0; i < outData.size(); i++) {
                        JSONObject acc = outData.getJSONObject(i);
                        if (acc.getBoolean("success")) {
                            String id = acc.getString("id");
                            if (sb.length() > 0)
                                sb.append(',');
                            sb.append(id);
                            hasSuccess = true;
                        } else {
                            System.err.println("update error: " + acc.getString("errors"));
                        }
                    }
                    if (hasSuccess) {
                        cobj.put("id", sb.toString());
                        sr.put("id", sb.toString());
                        invoker = new TriggerInvoker(userInfo, "update", "after");
                        invoker.handler(cobj);
                    }
                } else {
                    System.err.println("update failed to get ids parameter");
                }
            } else {
                String msg = relObj.getString("returnInfo");
                System.err.println("update error= " + msg);
                if (msg != null) {
                    sr.put("message", msg);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            sr.put("message", e.getMessage());
        }
        return sr;
    }

    /**
     * Public documentation "update" full parameter control version (4 booleans).
     */
    public ServiceResult update(CCObject cobj, boolean isRight, boolean isWorkFlow, boolean isBeforeTrigger,
            boolean isAfterTrigger) {
        return null;
    }

    /**
     * Public documentation "update" full parameter control version (including
     * validation rules + duplicate checking).
     */
    public ServiceResult update(CCObject cobj, boolean isRight, boolean isWorkFlow, boolean isBeforeTrigger,
            boolean isAfterTrigger, boolean isValidAndDuplication) {
        return null;
    }

    public ServiceResult insert(CCObject cobj) {
        ServiceResult sr = new ServiceResult();
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);

        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_INSERT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            TriggerInvoker invoker = new TriggerInvoker(userInfo, "insert", "before");
            invoker.handler(cobj);
            JSONObject relObj = invokeCommon(parObj);
            if ("true".equals(relObj.getString("result"))) {
                JSONArray outData = relObj.getJSONObject("data").getJSONArray("ids");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < outData.size(); i++) {
                    JSONObject acc = outData.getJSONObject(i);
                    if (acc.getBoolean("success")) {
                        if (sb.length() > 0)
                            sb.append(',');
                        sb.append(acc.getString("id"));
                    } else {
                        System.err.println("insert error: " + acc.getString("errors"));
                    }
                }
                String retIds = sb.toString();
                sr.put("id", retIds);
                cobj.put("id", retIds);
                invoker = new TriggerInvoker(userInfo, "insert", "after");
                invoker.handler(cobj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sr;
    }

    public ServiceResult insert(List<CCObject> datalist) {
        ServiceResult sr = new ServiceResult();
        if (datalist == null || datalist.isEmpty())
            return sr;

        JSONArray arry = new JSONArray();
        String objectApiName = "";
        for (CCObject cobj : datalist) {
            JSONObject obj = new JSONObject();
            for (String _key : cobj.keySet()) {
                if ("CCObjectAPI".equals(_key)) {
                    objectApiName = cobj.get("CCObjectAPI") == null ? "" : cobj.get("CCObjectAPI") + "";
                } else {
                    obj.put(_key, cobj.get(_key) == null ? "" : cobj.get(_key) + "");
                }
            }
            arry.add(obj);
        }
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_INSERT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            JSONObject relObj = invokeCommon(parObj);
            if ("true".equals(relObj.getString("result"))) {
                JSONArray outData = relObj.getJSONObject("data").getJSONArray("ids");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < outData.size(); i++) {
                    JSONObject acc = outData.getJSONObject(i);
                    if (acc.getBoolean("success")) {
                        if (sb.length() > 0)
                            sb.append(',');
                        sb.append(acc.getString("id"));
                    } else {
                        System.err.println("insert error: " + acc.getString("errors"));
                    }
                }
                sr.put("id", sb.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sr;
    }

    /**
     * Public documentation "delete" single record deletion, returns
     * {@link ServiceResult}.
     */
    public ServiceResult delete(CCObject cobj) {
        ServiceResult sr = new ServiceResult();
        try {
            TriggerInvoker invoker = new TriggerInvoker(userInfo, "delete", "before");
            invoker.handler(cobj);

            JSONArray arry = buildDataArray(cobj);
            String objectApiName = extractObjectApiName(cobj);
            JSONObject parObj = new JSONObject();
            parObj.put("serviceName", SVC_DELETE);
            parObj.put("objectApiName", objectApiName);
            parObj.put("data", arry.toString());
            JSONObject relObj = invokeCommon(parObj);
            boolean ok = Boolean.TRUE.equals(relObj.getBoolean("result"))
                    || "true".equals(relObj.getString("result"));
            if (ok) {
                sr.put("success", Boolean.TRUE);
            } else {
                String info = relObj.getString("returnInfo");
                if (info != null) {
                    sr.put("message", info);
                }
            }

            invoker = new TriggerInvoker(userInfo, "delete", "after");
            invoker.handler(cobj);
        } catch (Exception e) {
            System.err.println("delete error= " + e);
            sr.put("message", e.getMessage());
        }
        return sr;
    }

    /** Public documentation "delete" batch deletion. */
    public boolean delete(List<CCObject> ccobjs) {
        if (ccobjs == null || ccobjs.isEmpty()) {
            return true;
        }
        for (CCObject cobj : ccobjs) {
            ServiceResult r = delete(cobj);
            if (r == null || r.get("success") == null || !Boolean.TRUE.equals(r.get("success"))) {
                return false;
            }
        }
        return true;
    }

    public ServiceResult upsert(CCObject cobj) {
        ServiceResult sr = new ServiceResult();
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);

        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_UPSERT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            TriggerInvoker invoker = new TriggerInvoker(userInfo, "upsert", "before");
            invoker.handler(cobj);
            JSONObject relObj = invokeCommon(parObj);
            if ("true".equals(relObj.getString("result"))) {
                JSONArray outData = relObj.getJSONObject("data").getJSONArray("ids");
                JSONObject acc = outData.getJSONObject(0);
                if (acc.getBoolean("success")) {
                    sr.put("id", acc.getString("id"));
                    cobj.put("id", acc.getString("id"));
                }
                invoker = new TriggerInvoker(userInfo, "upsert", "after");
                invoker.handler(cobj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sr;
    }

    // -------- WithRoleRight CRUD variants --------

    public ServiceResult insertWithRoleRight(CCObject cobj) {
        return insertInternal(cobj, SVC_INSERT_WITH_ROLE_RIGHT);
    }

    /**
     * Corresponding to public documentation "updateWithRoleRight": returns
     * {@code "success"} on success, {@code "failure:..."} on failure.
     */
    public String updateWithRoleRight(CCObject cobj) {
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_UPDATE_WITH_ROLE_RIGHT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            JSONObject relObj = invokeCommon(parObj);
            if (Boolean.TRUE.equals(relObj.getBoolean("result"))) {
                JSONObject outObj = relObj.getJSONObject("data");
                if (outObj != null && outObj.containsKey("ids")) {
                    JSONArray outData = outObj.getJSONArray("ids");
                    for (int i = 0; i < outData.size(); i++) {
                        JSONObject acc = outData.getJSONObject(i);
                        if (acc.getBoolean("success")) {
                            return "success";
                        }
                    }
                }
                return "success";
            }
            String info = relObj.getString("returnInfo");
            return info != null && !info.isEmpty() ? "failure:" + info : "failure";
        } catch (Exception e) {
            e.printStackTrace();
            return "failure:" + e.getMessage();
        }
    }

    /**
     * Corresponding to public documentation "deleteWithRoleRight": returns
     * {@code "success"} on success, error message on failure.
     */
    public String deleteWithRoleRight(CCObject cobj) {
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_DELETE_WITH_ROLE_RIGHT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            JSONObject relObj = invokeCommon(parObj);
            if ("true".equals(relObj.getString("result"))) {
                return "success";
            }
            String info = relObj.getString("returnInfo");
            return info != null ? info : "failure";
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage() != null ? e.getMessage() : "failure";
        }
    }

    public ServiceResult upsertWithRoleRight(CCObject cobj) {
        ServiceResult sr = new ServiceResult();
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", SVC_UPSERT_WITH_ROLE_RIGHT);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            JSONObject relObj = invokeCommon(parObj);
            if ("true".equals(relObj.getString("result"))) {
                JSONArray outData = relObj.getJSONObject("data").getJSONArray("ids");
                JSONObject acc = outData.getJSONObject(0);
                if (acc.getBoolean("success")) {
                    sr.put("id", acc.getString("id"));
                    cobj.put("id", acc.getString("id"));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sr;
    }

    private ServiceResult insertInternal(CCObject cobj, String serviceName) {
        ServiceResult sr = new ServiceResult();
        JSONArray arry = buildDataArray(cobj);
        String objectApiName = extractObjectApiName(cobj);
        JSONObject parObj = new JSONObject();
        parObj.put("serviceName", serviceName);
        parObj.put("objectApiName", objectApiName);
        parObj.put("data", arry.toString());
        try {
            JSONObject relObj = invokeCommon(parObj);
            if ("true".equals(relObj.getString("result"))) {
                JSONArray outData = relObj.getJSONObject("data").getJSONArray("ids");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < outData.size(); i++) {
                    JSONObject acc = outData.getJSONObject(i);
                    if (acc.getBoolean("success")) {
                        if (sb.length() > 0)
                            sb.append(',');
                        sb.append(acc.getString("id"));
                    } else {
                        System.err.println(serviceName + " error: " + acc.getString("errors"));
                    }
                }
                sr.put("id", sb.toString());
                cobj.put("id", sb.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sr;
    }

    // -------- HTTP helpers --------

    public String sendPost(String path, String param) {
        return doPost(path, param, null);
    }

    public String CCPost(String path, String param) {
        return doPost(path, param, ACCESSTOKEN);
    }

    /** Corresponding to public documentation "getAccessToken". */
    public String getAccessToken() {
        return ACCESSTOKEN;
    }

    private String doPost(String urlStr, String param, String accessToken) {
        OutputStreamWriter out = null;
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        String exception = "";
        try {
            URL url = new URL(urlStr);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(15 * 1000);
            conn.setReadTimeout(20 * 1000);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("accept", "*/*");
            if (accessToken != null) {
                conn.setRequestProperty("accessToken", accessToken);
            }
            conn.connect();
            out = new OutputStreamWriter(conn.getOutputStream());
            BufferedWriter bw = new BufferedWriter(out);
            bw.write(param);
            bw.flush();
            br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            System.out.println("Error sending POST request! " + e);
            exception = "Error sending POST request! " + e;
            e.printStackTrace();
        } finally {
            try {
                if (out != null)
                    out.close();
                if (br != null)
                    br.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return sb.length() == 0 ? exception : sb.toString();
    }

    public static String sendGet(String path) {
        StringBuilder sb = new StringBuilder();
        String exception = "";
        try {
            URL url = new URL(path);
            InputStream is = url.openStream();
            InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
            BufferedReader br = new BufferedReader(isr);
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            isr.close();
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
            exception = "Error sending GET request! " + e;
        }
        return sb.length() == 0 ? exception : sb.toString();
    }

    // -------- CustomSetting --------

    public Map<String, Object> getListCustomSetting(String api) {
        Map<String, Object> datMap = new HashMap<String, Object>();
        JSONArray argArray = new JSONArray();
        JSONObject arryStr = new JSONObject();
        arryStr.put("argType", String.class);
        arryStr.put("argValue", api);
        argArray.add(arryStr);

        JSONObject parObj = new JSONObject();
        parObj.put("className", "TCCCustomSetting");
        parObj.put("methodName", "getListCustomSetting");
        parObj.put("params", argArray);
        try {
            String relStr = CCPost(path, parObj.toString());
            JSONObject relObj = JSONObject.parseObject(relStr);
            if ("true".equals(relObj.getString("result"))) {
                JSONObject data = relObj.getJSONObject("data");
                if (data != null) {
                    datMap.putAll(data);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datMap;
    }

    public Map<String, Object> getListCustomSetting(String api, String key) {
        Map<String, Object> datMap = new HashMap<String, Object>();
        JSONArray argArray = new JSONArray();

        JSONObject apiParam = new JSONObject();
        apiParam.put("argType", String.class);
        apiParam.put("argValue", api);
        argArray.add(apiParam);

        JSONObject keyParam = new JSONObject();
        keyParam.put("argType", String.class);
        keyParam.put("argValue", key);
        argArray.add(keyParam);

        JSONObject parObj = new JSONObject();
        parObj.put("className", "TCCCustomSetting");
        parObj.put("methodName", "getListCustomSetting");
        parObj.put("params", argArray);
        try {
            String relStr = CCPost(path, parObj.toString());
            JSONObject relObj = JSONObject.parseObject(relStr);
            if ("true".equals(relObj.getString("result"))) {
                JSONObject data = relObj.getJSONObject("data");
                if (data != null) {
                    datMap.putAll(data);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datMap;
    }

    // -------- Extended API: aligned with "CCService_SDK_PublicDoc";
    // implementations not connected to OpenAPI return placeholder values --------

    /**
     * Public documentation "insert" full parameter control version (6 booleans).
     */
    public ServiceResult insert(CCObject cobj, boolean isRight, boolean isWorkFlow, boolean isBeforeTrigger,
            boolean isAfterTrigger, boolean isValidAndDuplication) {
        return null;
    }

    /** Public documentation "insertWithWorkflow". */
    public ServiceResult insertWithWorkflow(CCObject ccobj) throws Exception {
        return null;
    }

    /** Public documentation "insertWithValidationAndDuplication". */
    public ServiceResult insertWithValidationAndDuplication(CCObject ccobj) {
        return null;
    }

    /** Public documentation "insertNoException". */
    @SuppressWarnings("rawtypes")
    public ServiceResult insertNoException(List<CCObject> ccobjs) {
        return null;
    }

    /**
     * Public documentation "insertLt" lightweight insert. Currently still uses
     * {@link #insert(List)}; a separate OpenAPI is needed to align with the
     * platform's "skip triggers" semantics.
     */
    public ServiceResult insertLt(CCObject ccobj) throws Exception {
        List<CCObject> datalist = new ArrayList<CCObject>();
        datalist.add(ccobj);
        return insert(datalist);
    }

    /** Public documentation "upsertLt". */
    public ServiceResult upsertLt(CCObject ccobj) {
        return null;
    }

    public ServiceResult updateLt(CCObject objectData) throws Exception {
        return null;
    }

    /** Public documentation "updateWithWorkFlow". */
    public ServiceResult updateWithWorkFlow(CCObject ccobj) throws Exception {
        return null;
    }

    /** Public documentation "updateWithValidationAndDuplication". */
    public ServiceResult updateWithValidationAndDuplication(CCObject ccobj) {
        return null;
    }

    /** Public documentation "batchInsert". */
    public ServiceResult batchInsert(List<CCObject> ccobjs) {
        return null;
    }

    /** Public documentation "batchInsert" full parameter version. */
    public ServiceResult batchInsert(List<CCObject> ccobjs, boolean isRight, boolean isWorkFlow, boolean isTrigger,
            boolean isValidAndDuplication) {
        return null;
    }

    /** Public documentation "batchUpdate". */
    public ServiceResult batchUpdate(List<CCObject> ccobjs) {
        return null;
    }

    /** Public documentation "batchUpdate" full parameter version. */
    public ServiceResult batchUpdate(List<CCObject> ccobjs, boolean isRight, boolean isWorkFlow, boolean isTrigger,
            boolean isValidAndDuplication) {
        return null;
    }

    /** Public documentation "batchDelete". */
    public ServiceResult batchDelete(List<CCObject> ccobjs) {
        return null;
    }

    /** Public documentation "batchDelete" with permissions. */
    public ServiceResult batchDelete(List<CCObject> ccobjs, boolean isRight) {
        return null;
    }

    /** Public documentation "insertWithRoleRightForBatch". */
    @SuppressWarnings("rawtypes")
    public List<Map> insertWithRoleRightForBatch(List<CCObject> ccobjs) {
        return null;
    }

    /** Public documentation "insertForBatch". */
    @SuppressWarnings("rawtypes")
    public List<Map> insertForBatch(List<CCObject> ccobjs, boolean isRight, boolean isWorkFlow,
            boolean isBeforeTrigger, boolean isAfterTrigger, boolean isValidAndDuplication) {
        return null;
    }

    /** Public documentation "updateWithRoleRightForBatch". */
    @SuppressWarnings("rawtypes")
    public List<Map> updateWithRoleRightForBatch(List<CCObject> ccobjs) {
        return null;
    }

    /** Public documentation "updateForBatch". */
    @SuppressWarnings("rawtypes")
    public List<Map> updateForBatch(List<CCObject> ccobjs, boolean isRight, boolean isWorkFlow,
            boolean isBeforeTrigger, boolean isAfterTrigger, boolean isValidAndDuplication) {
        return null;
    }

    /** Public documentation "submitForApproval". */
    public String submitForApproval(String relateId, String fprId, String appPath) {
        return null;
    }

    /** Public documentation "process" approval processing. */
    public String process(String processType, String workItemId, String fprId, String comments, String appPath) {
        return null;
    }

    /** Public documentation "recall". */
    public String recall(String relateId, String comments) {
        return null;
    }

    /** Public documentation "showPendingHis". */
    @SuppressWarnings("rawtypes")
    public List<Map> showPendingHis(String userId) {
        return null;
    }

    /** Public documentation "getApprovalPendingSize". */
    public String getApprovalPendingSize() {
        return null;
    }

    /** Public documentation "addMicroPost" basic version. */
    public String addMicroPost(String feedType, String linkName, String linkValue, String fileName, String fileType,
            InputStream fileStream, File file, String bodyType, String body, String targetType, String targetId,
            String recordId) {
        return null;
    }

    /** Public documentation "addMicroPost" with geolocation version. */
    public String addMicroPost(String feedType, String linkName, String linkValue, String fileName, String fileType,
            InputStream fileStream, File file, String bodyType, String body, String targetType, String targetId,
            String recordId, String longitude, String latitude, String address, String taskIdOrEventId) {
        return null;
    }

    /** Public documentation "addMicroComment". */
    public String addMicroComment(String feedid, String fileName, String fileType, InputStream fileStream,
            String body) {
        return null;
    }

    /** Public documentation "addMicroComment" with geolocation. */
    public String addMicroComment(String feedid, String fileName, String fileType, InputStream fileStream,
            String body, String longitude, String latitude, String address) {
        return null;
    }

    /** Public documentation "getChatters". */
    @SuppressWarnings("rawtypes")
    public List<Map> getChatters(String queryType, String userId, String feedid, String recordId, String feedsort,
            String limit, String skip) {
        return null;
    }

    /** Public documentation "praiseFeed". */
    public String praiseFeed(String feedid, String type, String iscomments) {
        return null;
    }

    /** Public documentation "uploadFile". */
    public String uploadFile(Map<String, Object> fileInfo, InputStream inputStream) {
        return null;
    }

    /** Public documentation "insertFile". */
    public String insertFile(Map<String, Object> fileInfo, InputStream inputStream) {
        return null;
    }

    /** Public documentation "downloadFile". */
    @SuppressWarnings("rawtypes")
    public Map downloadFile(String fileId) {
        return null;
    }

    /** Public documentation "getFile". */
    @SuppressWarnings("rawtypes")
    public Map getFile(String fileId) {
        return null;
    }

    /** Public documentation "queryFileList". */
    @SuppressWarnings("rawtypes")
    public List<Map> queryFileList(Map conditionMap) {
        return null;
    }

    /** Public documentation "cqueryFileField". */
    public List<CCObject> cqueryFileField(String recordId, String fieldApiName) {
        return null;
    }

    /** Public documentation "sendEmail" template version. */
    public String sendEmail(String templateId, String toaddress, String ccaddress, String bcaddress, String relatedid) {
        return null;
    }

    /** Public documentation "sendEmail" custom without CC. */
    public String sendEmail(String name, String htmlbody, String toaddress, String relatedid) {
        return null;
    }

    /** Public documentation "sendEmailWithccaddress". */
    public String sendEmailWithccaddress(String name, String htmlbody, String toaddress, String relatedid,
            String ccaddress) {
        return null;
    }

    /** Public documentation "sendEmailWithAttachment". */
    public String sendEmailWithAttachment(String name, String htmlbody, String toaddress, String relatedid,
            Object attachment) {
        return null;
    }

    /** Public documentation "sendEmail" full parameter version. */
    public String sendEmail(String name, String htmlbody, String toaddress, String relatedid, String ccaddress,
            String bccaddress, Object attachment, String istrackopen, String singleSend) {
        return null;
    }

    /** Public documentation "sendEmailWithFile". */
    public ServiceResult sendEmailWithFile(String content, String subject, String toaddress, String ccaddress,
            String bcaddress, List<File> fileList, List<InputStream> inputList, List<byte[]> byteList) {
        return null;
    }

    /** Public documentation "queryEmailIn" two-parameter version. */
    @SuppressWarnings("rawtypes")
    public List<Map> queryEmailIn(String startDate, String endDate) {
        return null;
    }

    /** Public documentation "queryEmailIn" multi-condition version. */
    @SuppressWarnings("rawtypes")
    public List<Map> queryEmailIn(List<String> userIds, List<String> emails, String startDate, String endDate) {
        return null;
    }

    /** Public documentation "queryEmailOut". */
    @SuppressWarnings("rawtypes")
    public List<Map> queryEmailOut(String startDate, String endDate) {
        return null;
    }

    /** Public documentation "queryEmail". */
    @SuppressWarnings("rawtypes")
    public List<Map> queryEmail(String incoming, List<String> userIds, List<String> emails, String startDate,
            String endDate) {
        return null;
    }

    /** Public documentation "getViewList". */
    @SuppressWarnings("rawtypes")
    public List<Map> getViewList(String objectApiName) {
        return null;
    }

    /** Public documentation "getViewListNew". */
    @SuppressWarnings("rawtypes")
    public List<Map> getViewListNew(String objectApiName) {
        return null;
    }

    /** Public documentation "queryByViewId". */
    @SuppressWarnings("rawtypes")
    public Map queryByViewId(String viewId, String pages, String pageSize, String sort, String dir, String keyWord,
            String fieldApis) {
        return null;
    }

    /** Public documentation "searchByViewId" eight-parameter version. */
    @SuppressWarnings("rawtypes")
    public Map searchByViewId(String viewId, String pages, String pageSize, String sort, String dir, String keyword,
            String fieldApis, String expression) {
        return null;
    }

    /**
     * Public documentation "searchByViewId" nine-parameter version (with objid).
     */
    @SuppressWarnings("rawtypes")
    public Map searchByViewId(String objid, String viewId, String pages, String pageSize, String sort, String dir,
            String keyword, String fieldApis, String expression) {
        return null;
    }

    /** Public documentation "getCustomSetting". */
    @SuppressWarnings("rawtypes")
    public Map getCustomSetting(String objectApiName, String id) {
        return null;
    }

    /** Public documentation "getPicklistValue". */
    @SuppressWarnings("rawtypes")
    public List<Map> getPicklistValue(String objectApiName, String fieldApi, String recordtype) {
        return null;
    }

    /** Public documentation "getObjectRight". */
    public String getObjectRight(String objectApiName) {
        return null;
    }

    /** Public documentation "getCurrencyRateInfos". */
    @SuppressWarnings("rawtypes")
    public List<Map> getCurrencyRateInfos() {
        return null;
    }

    /** Public documentation "executeAsyncJob". */
    @SuppressWarnings("rawtypes")
    public ServiceResult executeAsyncJob(Map paramMap) {
        return null;
    }

    /** Public documentation "executeAsyncCustomClass". */
    @SuppressWarnings("rawtypes")
    public ServiceResult executeAsyncCustomClass(Map paramMap) {
        return null;
    }

    /** Public documentation "queryTianyancha". */
    public ServiceResult queryTianyancha(String searchword) {
        return null;
    }

    /** Public documentation "tianYanChaDetailInfo". */
    public ServiceResult tianYanChaDetailInfo(String keyword, String id) {
        return null;
    }

    /** Public documentation "deleteShareObjectBySql". */
    public void deleteShareObjectBySql(String objectApiName, String expression) {
    }

    // -------- Helper methods --------

    static JSONArray buildDataArray(CCObject cobj) {
        JSONArray arry = new JSONArray();
        JSONObject obj = new JSONObject();
        for (String _key : cobj.keySet()) {
            if (!"CCObjectAPI".equals(_key)) {
                obj.put(_key, cobj.get(_key) == null ? "" : cobj.get(_key) + "");
            }
        }
        arry.add(obj);
        return arry;
    }

    static String extractObjectApiName(CCObject cobj) {
        return cobj.get("CCObjectAPI") == null ? "" : cobj.get("CCObjectAPI") + "";
    }

    // -------- Inner classes --------

    public static class PageQueryResult {
        public final List<CCObject> records;
        public final JSONObject raw;

        public PageQueryResult(List<CCObject> records, JSONObject raw) {
            this.records = records;
            this.raw = raw;
        }

        public int getPageNum() {
            JSONObject data = raw != null ? raw.getJSONObject("data") : null;
            return data != null ? data.getIntValue("pageNum") : 0;
        }

        public int getPageSize() {
            JSONObject data = raw != null ? raw.getJSONObject("data") : null;
            return data != null ? data.getIntValue("pageSize") : 0;
        }

        public long getTotalCount() {
            JSONObject data = raw != null ? raw.getJSONObject("data") : null;
            return data != null ? data.getLongValue("totalCount") : 0L;
        }

        public int getPageCount() {
            JSONObject data = raw != null ? raw.getJSONObject("data") : null;
            return data != null ? data.getIntValue("pageCount") : 0;
        }

        public boolean isHasPre() {
            JSONObject data = raw != null ? raw.getJSONObject("data") : null;
            return data != null && Boolean.TRUE.equals(data.getBoolean("hasPre"));
        }

        public boolean isHasNext() {
            JSONObject data = raw != null ? raw.getJSONObject("data") : null;
            return data != null && Boolean.TRUE.equals(data.getBoolean("hasNext"));
        }
    }
}
