package com.ch.jedge.jbot.intent;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ch.jedge.jbot.JedgeLlmUtil;
import com.changhong.jedge.JMgbusModual;
import com.changhong.jedge.JMgbusService;
import com.changhong.jedge.JMgbusUtil;
import com.changhong.jedge.MgbusApi;
import com.changhong.qlib.QData;
import com.changhong.qlib.intf.QIData;
import com.changhong.qlib.intf.QIDataList;
import com.changhong.qlib.util.StringUtils;
import com.changhong.qlib.util.sync.SystemUtils;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.ch.jedge.jbot.JEJBotLLMConst.sval_default_jbot_jedge;

public class JedgeJBotCBService extends JMgbusService {

    private boolean is_active_ = false;
    private boolean is_online_ = false;
    private final Object quitCtrl = new Object();
    private final Map<String, Integer> registeredDomainMap = new HashMap<>();
    private final Map<String, Integer> registeredDataSetMap = new HashMap<>();
    private final Map<String, Method> DataGetterMap = new HashMap<>();

    public JedgeJBotCBService(JMgbusModual holder) {
        super(holder);
        auto_remove_call_info = false;
        watchLocalEvent("OnModuleConnected", (w, keyVal, msg) -> {
            String host = msg.getString("host");
            int port = msg.getInteger("port");
            is_online_ = true;
            watchMgbusEvent("online", "jbot!", qiData -> {
                synchronized (quitCtrl) {
                    if (!is_active_) {
                        is_active_= true;
                        module.postThread(this::refreshJBotRegister);
                    }
                }
                synchronized (quitCtrl) {
                    quitCtrl.notifyAll();
                }
            });
            //要注意定时刷新
            return true;
        });

        watchLocalEvent("OnModuleDisconnected", (w, keyVal, msg) -> {
            String host = msg.getString("host");
            int port = msg.getInteger("port");
            is_online_ = false;
            synchronized (quitCtrl) {
                quitCtrl.notifyAll();
            }
            return true;
        });
    }

    @Override
    public void onServiceStop() {
        is_online_ = false;
        quitCtrl.notifyAll();
        super.onServiceStop();
    }

    private void refreshJBotRegister() {
        while(is_online_) {
            prepareDataSetMethods();
            autoRegisterJBotAPIDomains();
            autoRegisterJBotDataSets();
            SystemUtils.tryWaitforSingal(quitCtrl, 90 * 1000);
        }
        is_active_ = false;
    }

    private void prepareDataSetMethods() {
        Class<?> cls = this.getClass();
        while(JMgbusService.class.isAssignableFrom(cls)) {
            Method[] methods = cls.getDeclaredMethods();
            for (Method m : methods) {
                JDataGetter a = m.getAnnotation(JDataGetter.class);
                if (a != null) {
                    Class<?> r = m.getReturnType();
                    Class<?>[] pm = m.getParameterTypes();
                    if (List.class.isAssignableFrom(r) && pm.length == 0) {
                        registerJBotDataGetter(a, m.getName(), m);
                    }
                }
            }
            cls = cls.getSuperclass();
        }
    }

    private void autoRegisterJBotDataSets() {
        Class<?> cls = this.getClass();
        while(JedgeJBotCBService.class.isAssignableFrom(cls)) {
            Method[] methods = cls.getDeclaredMethods();
            for (Method m : methods) {
                JbotData a = m.getAnnotation(JbotData.class);
                if (a != null) {
                    Class<?> r = m.getReturnType();
                    Class<?>[] pm = m.getParameterTypes();
                    if (List.class.isAssignableFrom(r) && pm.length == 0) {
                        String cbUri = String.format("/%s/%s", getName(), m.getName());
                        registerJBotDataSet(a, m.getName(), module.getName(), cbUri);
                    }
                }
            }
            cls = cls.getSuperclass();
        }
    }


    protected void autoRegisterJBotAPIDomains() {
        Class<?> cls = this.getClass();
        while(JMgbusService.class.isAssignableFrom(cls)) {
            Method[] methods = cls.getDeclaredMethods();
            for (Method m : methods) {
                JbotApi a = m.getAnnotation(JbotApi.class);
                if (a != null) {
                    Class<?> r = m.getReturnType();
                    Class<?>[] pm = m.getParameterTypes();
                    if (QIData.class.isAssignableFrom(r) && pm.length == 1 && QIData.class.isAssignableFrom(pm[0])) {
                        String cbUri = String.format("/%s/%s", getName(), m.getName());
                        registerJBotAPIDomain(a, module.getName(), cbUri);
                    }
                }
            }
            cls = cls.getSuperclass();
        }
    }

    private void registerJBotAPIDomain(JbotApi a, String moduleName, String cbUri) {
        String domain = a.domain();
        String dataset = a.dataset();   //创建API时需要的数据集
        String disc = a.disc();
        String role = a.role();
        String format = a.msgFmt();     //消息格式，其中字段，需要对应于数据集
        QIData req = new QData().putString("cbUri", cbUri).putString("src", moduleName)
                .putString("key", domain).putString("dmd", disc).putString("rl", role)
                .putString("pf", format).putString("ds", dataset);
        synchronized (registeredDomainMap){
            if(registeredDomainMap.containsKey(domain)) {
                req.putInteger("dmid", registeredDomainMap.get(domain));
            }
        }
        QIData re = postServiceRequest(sval_default_jbot_jedge, "/gpt/registerDomain", req);
        if(!JMgbusUtil.isMgbusResultOk(re)) {
            module.errLog(String.format("Fail to register Domain (%s) to %s : %s " , domain, sval_default_jbot_jedge, re.getString("msg")));
        } else {
            int dmId = re.getInteger("dmid");
            if(dmId>-1) {
                synchronized (registeredDomainMap) {
                    registeredDomainMap.put(domain, dmId);  //也可能更新module
                }
            }
            module.highLog(String.format("领域模块注册成功:%s (%d)", domain, dmId));
        }
    }

    private void updateJBotDataSet(String domain, String words, JSONArray dataset) {
        //更新数据集
        QIData req = new QData().putString("domain", domain).putString("key", words).put("payload",dataset);
        synchronized (registeredDomainMap) {
            if(!registeredDomainMap.containsKey(domain)) {
                module.errLog(String.format("Fail to register DataSet (%s) to %s : domain %s 尚未注册 " , words, sval_default_jbot_jedge, domain));
                return;
            }
            req.putInteger("dmid", registeredDomainMap.get(domain));
        }

        int dsId;
        synchronized (registeredDataSetMap){
            if(!registeredDataSetMap.containsKey(words)) {
                module.errLog(String.format("Fail to register DataSet (%s) to %s : domain %s 尚未注册 " , words, sval_default_jbot_jedge, domain));
                return;
            }
            dsId = registeredDomainMap.get(words);
            req.putInteger("dsid", dsId);
        }
        QIData re = postServiceRequest(sval_default_jbot_jedge, "/gpt/updateDataset", req);
        if(!JMgbusUtil.isMgbusResultOk(re)) {
            module.errLog(String.format("Fail to update DataSet (%s) to %s : %s " , words, sval_default_jbot_jedge, re.getString("msg")));
        } else {
            module.highLog(String.format("数据集更新成功:%s(%d)", words, dsId));
        }
    }

    private void registerJBotDataSet(JbotData a, String initSet, String moduleName, String cbUri) {
        String words = a.words();
        String domain = a.domain();
        boolean isNullable = a.nullable();
        // 调用初始化函数得到
        JSONArray initData = null;
        synchronized (DataGetterMap) {
            Method m = DataGetterMap.get(initSet);
            if (m != null) {
                try {
                    Object re = m.invoke(this);
                    if(re instanceof List) {
                        initData =JedgeLlmUtil.JsonArrayFromStringList((List<String>)re);
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }

        QIData req = new QData().putString("domain", domain).putString("cbUri", cbUri)
                .putString("src", moduleName).putString("key", words)
                .putBoolean("na", isNullable);
        if(initData!=null) req.put("payload", initData);
        synchronized (registeredDomainMap){
            if(registeredDomainMap.containsKey(domain)) {
                req.putInteger("dmid", registeredDomainMap.get(domain));
            } else {
                module.errLog(String.format("Fail to register DataSet (%s) to %s : domain %s 尚未注册 " , words, sval_default_jbot_jedge, domain));
                return;
            }
        }
        synchronized (registeredDataSetMap) {
            if(registeredDataSetMap.containsKey(words)) {
                req.putInteger("dsid", registeredDataSetMap.get(words));
            }
        }
        QIData re = postServiceRequest(sval_default_jbot_jedge, "/gpt/registerDataset", req);
        if(!JMgbusUtil.isMgbusResultOk(re)) {
            module.errLog(String.format("Fail to register DataSet (%s) to %s : %s " , words, sval_default_jbot_jedge, re.getString("msg")));
        } else {
            int dsId = re.getInteger("dsid");
            if(dsId>-1) {
                synchronized (registeredDataSetMap) {
                    registeredDataSetMap.put(words, dsId);  //也可能更新module
                }
            }
            module.highLog(String.format("数据集注册成功:%s(%d)", words, dsId));
        }
    }


    //刷新数据集(从Jbot向本地重新请求数据)
    @MgbusApi
    public QIData refreshDataSet(QIData eMsg) {
        String dsKey = eMsg.getKey();
        module.warnLog("Fresh Data set by : %s", dsKey);    //从数据集名称到dsKey名称
        if(StringUtils.isValidStr(dsKey)) {
            JSONArray initData = null;
            synchronized (DataGetterMap) {
                Method m = DataGetterMap.get(dsKey);
                if (m != null) {
                    try {
                        Object re = m.invoke(this);
                        if(re instanceof List) {
                            initData =JedgeLlmUtil.JsonArrayFromStringList((List<String>)re);
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
            return JMgbusUtil.MgbusResult(200, "").put("payload", initData);
        }
        //
        return JMgbusUtil.MgbusResult(401, "No Key");
    }


    private void registerJBotDataGetter(JDataGetter a, String DGetterKey, Method m) {
        synchronized (DataGetterMap) {
            DataGetterMap.put(DGetterKey, m);
        }
    }

    public static List<String> loadDataSetFromString(String dsStr) {
        List<String> re = new ArrayList<>();
        QIData d  = QData.fromString(dsStr);
        if(d instanceof QIDataList) {
            for(Object o : ((QIDataList) d).asJsonArray()) {
                if(o instanceof JSONObject) {
                    re.add(((JSONObject) o).toJSONString());
                }
            }
        } else {
            re.add(d.toJSONString());
        }
        return re;
    }

    //两种情况,一种是json,一种是每行是json
    public static List<String> loadDataSetFromFile(String fn) {
        File dsFile = new File(fn);
        if(dsFile.exists()) {
            if(fn.endsWith(".ds")) {
                List<String> lines = StringUtils.readStringLinesFromFile(fn);
                List<String> re = new ArrayList<>();
                for(String l : lines) {
                    l = l.trim();
                    if(StringUtils.isNotValidStr(l) || l.charAt(0) == '#') continue;
                    QIData d = QData.fromString(l);
                    re.add(d.toJSONString());
                }
                return re;
            } else if(fn.endsWith(".json")) {
                String dsStr = StringUtils.readStringFromFile(dsFile);
                return loadDataSetFromString(dsStr);
            }
        }
        return new ArrayList<>();
    }

}
