package com.sdtool.business.service;

import com.sdtool.business.dao.DocDao;
import com.sdtool.common.api.DocInfoService;
import com.sdtool.common.api.NoSqlKeysService;
import com.sdtool.common.base.BaseService;
import com.sdtool.common.datas.*;
import com.sdtool.common.entity.base.ProcessEntityItem;
import com.sdtool.common.entity.base.ProcessIdItem;
import com.sdtool.common.entity.base.ProcessSearchItem;
import com.sdtool.common.entity.base.ScriptContent;
import com.sdtool.common.entity.doc.BusinessScript;
import com.sdtool.common.entity.site.ERPTokenUser;
import com.sdtool.common.entity.system.CompanyInfo;
import com.sdtool.common.tool.ERPUtils;
import com.dySweetFishPlugin.elasticsearch.ESClient;
import com.dySweetFishPlugin.redis.RedisService;
import com.dySweetFishPlugin.sql.RMapUtils;
import com.dySweetFishPlugin.sql.TableIdService;
import com.dySweetFishPlugin.sql.dao.TunaService;
import com.dySweetFishPlugin.tool.lang.DateUtil;
import com.dySweetFishPlugin.tool.lang.ThreadFactoryWithNamePrefix;
import com.sweetfish.convert.json.JsonConvert;
import com.sweetfish.convert.json.JsonFactory;
import com.sweetfish.service.Local;
import com.sweetfish.service.RetResult;
import com.sweetfish.source.PageFlipper;
import com.sweetfish.util.AnyValue;
import com.sweetfish.util.AutoLoad;
import com.sweetfish.util.ResourceType;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.rex.RMap;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;

@AutoLoad(false)
@Local
@ResourceType(DocInfoService.class)
public class DocInfoServiceImpl extends BaseService implements DocInfoService {
    @Resource
    RedisService redisService;

    @Resource
    NoSqlKeysService keysService;

    @Resource
    ESClient esClient;

    @Resource
    JsonConvert jsonConvert;

    @Resource
    JsonFactory jsonRootFactory;

    @Resource
    TunaService tunaService;

    @Resource
    TableIdService tableIdService;

    private DocDao docDao;

    /**
     * 档案线程池
     */
    private ExecutorService docExecutor;

    private JsonConvert inquireInfoConvert;

    @Override
    public void init(AnyValue config) {
        super.init(config);

        docExecutor = Executors.newFixedThreadPool(4, new ThreadFactoryWithNamePrefix("[基础档案线程池]"));
    }

    @Override
    public void start(AnyValue config) {
        docDao = tunaService.generate(DocDao.class);
    }

    /**
     * 进程退出时，调用Service销毁
     * 远程模式下该方法会重载成空方法
     *
     * @param config 配置参数
     */
    @Override
    public void destroy(AnyValue config) {
        super.destroy(config);

        if (docExecutor != null) {
            docExecutor.shutdown();
        }
    }

    //region 业务脚本

    /**
     * 从redis中按code获取脚本
     * 脚本数据量不会太大，维护从数据库中获取即可，但redis中会存有code对应的信息，用于其他业务快速使用
     *
     * @param code         脚本编码
     * @param supplierCode 分表
     * @return 脚本信息
     */
    public BusinessScript getRedisBusinessScript(String code, long supplierCode) {
        String json = redisService.hget(keysService.getRedisKey(RedisKeys.KEY_BUSINESSSCRIPT, supplierCode), code);

        if (StringUtils.isEmpty(json)) {
            return null;
        }

        return jsonConvert.convertFrom(BusinessScript.class, json);
    }

    /**
     * 从code获取业务脚本信息
     *
     * @param code 脚本编码
     * @return 脚本信息
     */
    @Override
    @SuppressWarnings("rawtypes")
    public RMap getBusinessScriptInfo(String code, String dataSourceId, long supplierCode) {
        BusinessScript info = getRedisBusinessScript(code, supplierCode);

        return RMapUtils.successV2(info, null, null);
    }

    /**
     * 从redis中获取脚本名称
     *
     * @param code         脚本编码
     * @param supplierCode 分表
     * @return 脚本名称
     */
    public String getRedisBusinessScriptName(String code, long supplierCode) {
        BusinessScript businessScript = getRedisBusinessScript(code, supplierCode);
        if (businessScript == null) {
            return "";
        }

        return businessScript.getBusinessName();
    }

    /**
     * 分页获取业务脚本列表
     * 从数据库中获取数据
     *
     * @param searchData  查询条件
     * @param pageFlipper 分页参数
     * @return 业务脚本列表
     */
    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public RMap getBusinessScriptList(RMap searchData, PageFlipper pageFlipper, String dataSourceId, long supplierCode) {

        RMap params = new RMap();
        if (searchData != null) {
            params.putAll(searchData);
        }

        if (!params.containsKey("voidFlag")) {
            params.put("voidFlag", -1);
        }

        List<BusinessScript> scriptList = docDao.selectBusinessScript(params, dataSourceId, supplierCode, pageFlipper);

        return RMapUtils.successV2(scriptList, null, pageFlipper);
    }

    /**
     * 增加业务脚本
     *
     * @param businessScript 脚本信息
     * @param currentUser    操作用户
     * @param dataSourceId   分库
     * @param supplierCode   分表
     */
    public int addBusinessScriptLocal(BusinessScript businessScript, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (redisService.hexists(keysService.getRedisKey(RedisKeys.KEY_BUSINESSSCRIPT, supplierCode), businessScript.getBusinessCode())) {
            return -1;
        }

        businessScript.setId(tableIdService.getTableIdMulti("tbBusinessScript.id", 1, dataSourceId, String.valueOf(supplierCode)));
        businessScript.setVoidFlag(0);

        BusinessScript.create(businessScript, currentUser.getId());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_BUSINESSSCRIPT, supplierCode), businessScript.getBusinessCode(), jsonConvert.convertTo(businessScript));

        docDao.addBusinessScript(businessScript, dataSourceId, supplierCode);

        return 1;
    }

    /**
     * 增加业务脚本
     *
     * @param businessScript 脚本信息
     * @param currentUser    操作用户
     * @return 操作信息
     */
    @Override
    @SuppressWarnings("rawtypes")
    public RMap addBusinessScript(BusinessScript businessScript, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (currentUser == null) {
            return RMapUtils.error(HttpCode.UNAUTHORIZED.value(), "未登录");
        }

        if (StringUtils.isEmpty(businessScript.getBusinessCode())) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "业务代码未输入");
        }

        int iResult = addBusinessScriptLocal(businessScript, currentUser, dataSourceId, supplierCode);
        if (iResult == -1) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "业务代码已存在");
        }

        return RMapUtils.success();
    }

    /**
     * 修改业务脚本
     *
     * @param businessScript 业务脚本
     * @param currentUser    操作用户
     * @return 操作信息
     */
    @Override
    @SuppressWarnings("rawtypes")
    public RMap updateBusinessScript(BusinessScript businessScript, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (currentUser == null) {
            return RMapUtils.error(HttpCode.UNAUTHORIZED.value(), "未登录");
        }

        if (businessScript == null) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "请求数据错误");
        }

        if (businessScript.getId() == 0L) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "请求数据错误");
        }

        BusinessScript redisInfo = getRedisBusinessScript(businessScript.getBusinessCode(), supplierCode);
        if (redisInfo == null) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "不存在此业务代码的脚本信息");
        }
        //code不允许修改，因为代码中都使用到了
        if (redisInfo.getId() != businessScript.getId()) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "不允许修改业务代码或id提交错误");
        }

        BusinessScript.update(businessScript, currentUser.getId());

//        redisInfo.setBusinessCode(businessScript.getBusinessCode());
        redisInfo.setBusinessName(businessScript.getBusinessName());

        redisInfo.setUpdateBy(businessScript.getUpdateBy());
        redisInfo.setUpdateTime(businessScript.getUpdateTime());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_BUSINESSSCRIPT, supplierCode), redisInfo.getBusinessCode(), jsonConvert.convertTo(redisInfo));

        docDao.updateBusinessScript(businessScript, dataSourceId, supplierCode);

        return RMapUtils.success();
    }

    /**
     * 启用、禁用业务脚本
     *
     * @param businessScript 业务脚本
     * @param currentUser    操作用户
     * @return 操作信息
     */
    @Override
    @SuppressWarnings("rawtypes")
    public RMap voidBusinessScript(BusinessScript businessScript, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (currentUser == null) {
            return RMapUtils.error(HttpCode.UNAUTHORIZED.value(), "未登录");
        }

        if (businessScript == null) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "请求数据错误");
        }

        if (businessScript.getId() == 0L) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "请求数据错误，id未传入");
        }
        if (StringUtils.isBlank(businessScript.getBusinessCode())) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "请求数据错误，businessCode未传入");
        }

        BusinessScript redisInfo = getRedisBusinessScript(businessScript.getBusinessCode(), supplierCode);
        if (redisInfo == null) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "不存在此业务代码的脚本信息");
        }

        BusinessScript.update(businessScript, currentUser.getId());

        redisInfo.setVoidFlag(businessScript.getVoidFlag());
        redisInfo.setUpdateBy(businessScript.getUpdateBy());
        redisInfo.setUpdateTime(businessScript.getUpdateTime());
        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_BUSINESSSCRIPT, supplierCode), redisInfo.getBusinessCode(), jsonConvert.convertTo(redisInfo));

        docDao.voidBusinessScript(businessScript, dataSourceId, supplierCode);

        return RMapUtils.success();
    }


    /**
     * 获取业务脚本脚本内容
     *
     * @param code 脚本编号
     * @return 脚本内容
     */
    @Override
    public RetResult<ScriptContent> getBusinessScriptContent(String code, String dataSourceId, long supplierCode) {

        BusinessScript redisInfo = getRedisBusinessScript(code, supplierCode);
        if (redisInfo == null) {
            return RetResult.<ScriptContent>errorT().retinfo("不存在此业务代码的脚本信息，可执行相关初始化脚本操作");
        }

        if (StringUtils.equalsIgnoreCase(code, "ERPLogin")) {
            return RetResult.<ScriptContent>errorT().retinfo("登录脚本不允许在线查看");
        }

        ScriptContent content = new ScriptContent();
        content.setCode(code);
        content.setModule(redisInfo.getBusinessModule());
        content.setModuleName(ERPModule.getModule(redisInfo.getBusinessModule()).getModuleDisplayName());

        String prefix = "BE_";

        File quoteScript = new File(businessScriptRoot + File.separator +
                supplierCode + File.separator +
                ERPModule.getModule(redisInfo.getBusinessModule()).getSaveDirectory() + File.separator +
                prefix + code + ".groovy");
        if (quoteScript.exists()) {
            content.setScriptBody(readUTF8File(quoteScript.toPath(), false));
        } else {
            content.setScriptBody("");
        }

        return RetResult.<ScriptContent>successT().result(content);
    }

    /**
     * 存储业务脚本脚本
     *
     * @param script 脚本内容
     * @return 操作信息
     */
    @Override
    public int saveBusinessScriptContent(ScriptContent script, boolean clearCache, String dataSourceId, long supplierCode, ERPTokenUser currentUser) {
        if (StringUtils.equalsIgnoreCase(script.getCode(), "ERPLogin")) {
            return 1;
        }

        if (redisService.setRedisSemaphore(keysService.getRedisKey(RedisKeys.KEY_SYSLOCK, supplierCode) + "businessscript:" + script.getCode(),
                "1",
                20)) {
            CompletableFuture.runAsync(() -> {
                //存储文件
                try {
                    String prefix = "BE_";

                    String fileName = businessScriptRoot + File.separator +
                            supplierCode + File.separator +
                            ERPModule.getModule(script.getModule()).getSaveDirectory() + File.separator +
                            prefix + script.getCode() + ".groovy";

                    Files.write(Paths.get(fileName),
                            Collections.singleton(new StringBuilder(script.getScriptBody())),
                            StandardCharsets.UTF_8,
                            StandardOpenOption.CREATE,
                            StandardOpenOption.WRITE,
                            StandardOpenOption.TRUNCATE_EXISTING);

                    if (clearCache) {
                        handleScript("DocClearScriptCache",
                                ERPModule.NONE,
                                dataSourceId, supplierCode,
                                () -> ProcessEntityItem.<ERPModuleEntity>newBuilder()
                                        .inputItem(new ERPModuleEntity(script.getCode(), ERPModule.getModule(script.getModule())))
                                        .currentUser(currentUser)
                                        .dataSourceId(dataSourceId)
                                        .supplierCode(supplierCode)
                                        .build());
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }

            }, docExecutor).whenComplete((v, e) -> {
                if (e != null) {
                    logger.error(e.getMessage(), e);
                }
            });

            return 1;
        } else {
            return -1;
        }
    }

    /**
     * 编译业务脚本脚本
     *
     * @param script 脚本内容
     * @return 编译结果
     */
    @Override
    @SuppressWarnings({"rawtypes"})
    public RMap compileBusinessScriptContent(ScriptContent script, String dataSourceId, long supplierCode) {
        if (StringUtils.isBlank(script.getScriptBody())) {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), "脚本内容为空");
        }

        String error = ERPUtils.checkGroovyScript(this.getClass().getClassLoader(),
                businessScriptRoot + File.separator +
                        supplierCode + File.separator +
                        ERPModule.getModule(script.getModule()).getSaveDirectory(),
                script.getScriptBody());
        if (StringUtils.isBlank(error)) {
            return RMapUtils.success();
        } else {
            return RMapUtils.error(HttpCode.BAD_REQUEST.value(), error);
        }

    }

    //endregion



    //region 分厂档案

    @Override
    public CompanyInfo getCompanyInfo(long id, long supplierCode) {
        return this.getRedisDocInfo(keysService.getRedisKey(RedisKeys.KEY_COMPANY_INFO, supplierCode),
                String.valueOf(id), CompanyInfo.class);
    }

    @Override
    public String getRedisCompanyInfoName(long id, long supplierCode) {
        CompanyInfo companyInfo = this.getCompanyInfo(id, supplierCode);
        if (companyInfo == null) {
            return "";
        }
        return companyInfo.getCompanyName();
    }

    @Override
    @SuppressWarnings({"rawtypes","unchecked"})
    public RetResult<List<CompanyInfo>> getCompanyInfoList(RMap searchData, PageFlipper pageFlipper, String dataSourceId, long supplierCode) {
        RMap params = new RMap();
        if (searchData != null) {
            params.putAll(searchData);
        }

        if (!params.containsKey("voidFlag")) {
            params.put("voidFlag", -1);
        }

        List<CompanyInfo> companyList = docDao.selectCompanyInfo(params, dataSourceId, supplierCode, pageFlipper);

        return RetResult.<List<CompanyInfo>>successT().result(companyList).page(pageFlipper);
    }

    @Override
    public RetResult<List<CompanyInfo>> getAllCompanyInfoList(String dataSourceId, long supplierCode) {
        Map<String, String> redisMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_COMPANY_INFO, supplierCode));

        List<CompanyInfo> infoList = redisMap.values().stream()
                .map((x) -> (CompanyInfo) jsonConvert.convertFrom(CompanyInfo.class, x))
                .filter((x) -> x.getVoidFlag() == 0)
                .sorted(Comparator.comparingLong(CompanyInfo::getId)).collect(Collectors.toList());

        return RetResult.<List<CompanyInfo>>successT().result(infoList);
    }

    @Override
    public RetResult<CompanyInfo> addCompanyInfo(CompanyInfo companyInfo, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        companyInfo.setId(tableIdService.getTableIdMulti("tbCompanyInfo.id", 1, dataSourceId, String.valueOf(supplierCode)));
        companyInfo.setVoidFlag(0);

        CompanyInfo.create(companyInfo, currentUser.getId());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_COMPANY_INFO, supplierCode), String.valueOf(companyInfo.getId()), jsonConvert.convertTo(companyInfo));

        docDao.addCompanyInfo(companyInfo, dataSourceId, supplierCode);
        return RetResult.successT();
    }

    @Override
    public RetResult<CompanyInfo> updateCompanyInfo(CompanyInfo companyInfo, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (companyInfo == null || companyInfo.getId() == 0L) {
            return RetResult.<CompanyInfo>errorT().retinfo("请求数据错误");
        }

        CompanyInfo redisInfo = getCompanyInfo(companyInfo.getId(), supplierCode);
        if (redisInfo == null) {
            return RetResult.<CompanyInfo>errorT().retinfo("不存在此id的分厂信息");
        }

        CompanyInfo.update(companyInfo, currentUser.getId());

        redisInfo.setCompanyName(companyInfo.getCompanyName());
        redisInfo.setAddress(companyInfo.getAddress());
        redisInfo.setLinkMan(companyInfo.getLinkMan());
        redisInfo.setLinkPhone(companyInfo.getLinkPhone());

        redisInfo.setUpdateBy(companyInfo.getUpdateBy());
        redisInfo.setUpdateTime(companyInfo.getUpdateTime());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_COMPANY_INFO, supplierCode), String.valueOf(redisInfo.getId()), jsonConvert.convertTo(redisInfo));

        docDao.updateCompanyInfo(companyInfo, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    @Override
    public RetResult<CompanyInfo> voidCompanyInfo(CompanyInfo companyInfo, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (companyInfo == null || companyInfo.getId() == 0L) {
            return RetResult.<CompanyInfo>errorT().retinfo("请求数据错误");
        }

        CompanyInfo redisInfo = getCompanyInfo(companyInfo.getId(), supplierCode);
        if (redisInfo == null) {
            return RetResult.<CompanyInfo>errorT().retinfo("不存在此id的分厂信息");
        }

        CompanyInfo.update(companyInfo, currentUser.getId());

        redisInfo.setVoidFlag(companyInfo.getVoidFlag());
        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_COMPANY_INFO, supplierCode), String.valueOf(redisInfo.getId()), jsonConvert.convertTo(redisInfo));

        docDao.voidCompanyInfo(companyInfo, dataSourceId, supplierCode);

        return RetResult.successT();
    }


    //endregion
}
