package cn.ibizlab.core.dict.extensions.service;

import cn.ibizlab.core.dict.domain.DictCatalog;
import cn.ibizlab.core.dict.domain.DictOption;
import cn.ibizlab.core.dict.filter.DictOptionSearchContext;
import cn.ibizlab.core.dict.service.IDictCatalogService;
import cn.ibizlab.core.dict.service.IDictOptionService;
import cn.ibizlab.util.dict.Catalog;
import cn.ibizlab.util.dict.CodeItem;
import cn.ibizlab.util.dict.CodeList;
import cn.ibizlab.util.dict.Option;
import cn.ibizlab.util.helper.DataObject;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class DictDstService
{
    @Autowired
    private IDictCatalogService dictCatalogService;

    @Autowired
    @Lazy
    private DictDstService proxy;

    @Autowired
    private IDictOptionService optionService;

    @Cacheable( value="dictcatalog",key = "'dict:'+#p0")
    public Catalog getDictCatalog(String code){
        Catalog catalog = new Catalog();
        CodeList rootCatalog=proxy.getCodeListCatalog(code);
        catalog.setCode(code).setName(rootCatalog.getName()).setAdvancedSettings(rootCatalog.getAdvancedSettings()).setNodes((List)JSON.toJSON(rootCatalog.getOptions()));
        return catalog;
    }
    public Catalog getDictCatalog(String code, DictOptionSearchContext context)
    {
        Catalog catalog = new Catalog();
        CodeList rootCatalog=proxy.getCodeListCatalog(code,context);
        catalog.setCode(code).setName(rootCatalog.getName()).setAdvancedSettings(rootCatalog.getAdvancedSettings()).setNodes((List)JSON.toJSON(rootCatalog.getOptions()));
        return catalog;
    }

    @Caching( evict = {
            @CacheEvict(value="dictcatalog",key = "'dict:'+#p0"),
            @CacheEvict(value="dictcatalog",key = "'glob:dict:'+#p0+'*'")
    })
    public void resetDictCatalog(String code)
    {

    }




    @Cacheable( value="dictcatalog",key = "'codelist:'+#p0")
    public CodeList getCodeListCatalog(String code){
        return getCodeListCatalog(code,new DictOptionSearchContext());
    }
    public CodeList getCodeListCatalog(String code, DictOptionSearchContext context)
    {
        CodeList catalog = new CodeList();
        if(code.equalsIgnoreCase("dictcatalog"))
        {
            catalog.setCode("DictCatalog");
            catalog.setName("字典目录");
            List<CodeItem> list = new ArrayList<>();
            LambdaQueryWrapper<DictCatalog> wrapper=Wrappers.lambdaQuery();
            if(!StringUtils.isEmpty(context.getN_val_like()))
                wrapper.like(DictCatalog::getCode,context.getN_val_like());
            if(!StringUtils.isEmpty(context.getN_val_in()))
                wrapper.in(DictCatalog::getCode,context.getN_val_in());
            if(!StringUtils.isEmpty(context.getN_val_leftlike()))
                wrapper.likeRight(DictCatalog::getCode,context.getN_val_leftlike());
            if(!StringUtils.isEmpty(context.getN_val_notin()))
                wrapper.notIn(DictCatalog::getCode,context.getN_val_notin());
            if(!StringUtils.isEmpty(context.getN_label_like()))
                wrapper.notIn(DictCatalog::getName,context.getN_label_like());
            if(!StringUtils.isEmpty(context.getN_vfilter_like()))
                wrapper.like(DictCatalog::getGroup,context.getN_vfilter_like());
            if(!StringUtils.isEmpty(context.getN_vfilter_in()))
                wrapper.in(DictCatalog::getGroup,context.getN_vfilter_in());
            if(!StringUtils.isEmpty(context.getN_vfilter_leftlike()))
                wrapper.likeRight(DictCatalog::getGroup,context.getN_vfilter_leftlike());
            if(!StringUtils.isEmpty(context.getN_vfilter_eq()))
                wrapper.eq(DictCatalog::getGroup,context.getN_vfilter_eq());
            wrapper.orderByAsc(DictCatalog::getGroup, DictCatalog::getCode);

            dictCatalogService.list(wrapper).forEach(item->{
                list.add(new CodeItem().setValue(item.getCode()).setId(item.getCode())
                        .setDisabled(false)
                        .setFilter(item.getGroup()).setLabel(item.getName())
                );
            });
            catalog.setOptions(list);
        }
        else {

            String codeName=code;
            String filter="";
            String subCode="";
            if(codeName.indexOf(".")>0)
            {
                String[] arg= codeName.split("[.]");
                codeName=arg[0];
                subCode=arg[1];
            }
            if(codeName.indexOf("-")>0)
            {
                String[] arg= codeName.split("-");
                codeName=arg[0];
                filter=arg[1];
            }
            QueryWrapper<DictOption> qw=context.getSelectCond();
            DictCatalog dictCatalog = dictCatalogService.getOne(Wrappers.<DictCatalog>query().eq("ccode",codeName));
            catalog.setCode(code).setName(dictCatalog.getName()).putAdvancedSettings(dictCatalog.getExtParams());
            if(!StringUtils.isEmpty(catalog.getUrl()))
            {
                Map<String,Object> urlParams=new HashMap<>();
                urlParams.putAll(context.getSessioncontext());
                urlParams.putAll(context.getDatacontext());
                urlParams.put("filter",filter);
                urlParams.put("subCode",subCode);
                urlParams.put("code",code);
                urlParams.put("codeName",codeName);
                catalog.setNodes((List)getRemoteDict(catalog,urlParams),filter,subCode);
            }
            else if( ((!StringUtils.isEmpty(filter))||(!StringUtils.isEmpty(subCode))) && qw.isEmptyOfWhere() )
            {
                CodeList rootCatalog=proxy.getCodeListCatalog(codeName);
                catalog.setCode(code).setName(rootCatalog.getName()).setAdvancedSettings(rootCatalog.getAdvancedSettings());
                catalog.setNodes((List)JSON.toJSON(rootCatalog.getOptions()),filter,subCode);
            }
            else
            {
                catalog.setNodes((List)JSON.toJSON(optionService.list(qw.eq("cid",dictCatalog.getId()).orderByAsc("showorder"))),filter,subCode);
            }
        }
        return catalog;
    }

    @Caching( evict = {
            @CacheEvict(value="dictcatalog",key = "'codelist:'+#p0"),
            @CacheEvict(value="dictcatalog",key = "'glob:codelist:'+#p0+'*'")
    })
    public void resetCodeListCatalog(String code)
    {

    }


    @Autowired
    private RestTemplate remoteDictClient;

    public JSONArray getRemoteDict(CodeList catalog,Map<String,Object> urlParams)
    {
        HttpHeaders headers = new HttpHeaders();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if(requestAttributes!=null)
        {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            String auth = request.getHeader("Authorization");
            if(!StringUtils.isEmpty(auth))
                headers.add("Authorization", auth);
        }


        String response=null;
        response=remoteDictClient.exchange(catalog.getUrl(),HttpMethod.valueOf(catalog.getRequestMethod().toUpperCase()),new HttpEntity(JSON.parse(catalog.getRequestBody()),headers),String.class,urlParams).getBody();

        Assert.notNull(response,"获取远程字典失败");
        JSONArray items=null;
        if((!StringUtils.isEmpty(catalog.getNodeProp()))||response.startsWith("{"))
        {
            String nodeProp=StringUtils.isEmpty(catalog.getNodeProp())?"items":catalog.getNodeProp();
            JSONObject res=JSON.parseObject(response);
            String pairs[] = nodeProp.split("[.]");
            for(int i=0;i<pairs.length;i++)
            {
                String pair=pairs[i].trim();
                if(StringUtils.isEmpty(pair))
                {
                    if(i<pairs.length-1) {
                        res = res.getJSONObject(pair);
                        Assert.notNull(res, "获取远程字典失败：" + pair + "未找到");
                    }
                    else
                        items=res.getJSONArray(pair);
                }
            }
        }
        else
        {
            items=JSON.parseArray(response);
        }
        Assert.notNull(items, "获取远程字典失败：数据项未找到");
        return items;
    }

    @Bean
    @LoadBalanced
    public RestTemplate remoteDictClient() {
        // 长连接
        PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager();
        // 总连接数
        pollingConnectionManager.setMaxTotal(1000);
        // 同路由的并发数
        pollingConnectionManager.setDefaultMaxPerRoute(1000);

        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        httpClientBuilder.setConnectionManager(pollingConnectionManager);
        // 重试次数，默认是3次，没有开启
        // httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3,
        // true));
        HttpClient httpClient = httpClientBuilder.build();

        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
                httpClient);
        // 连接超时
        clientHttpRequestFactory.setConnectTimeout(60000);
        // 数据读取超时时间，即SocketTimeout
        clientHttpRequestFactory.setReadTimeout(60000);
        // 连接不够用的等待时间，不宜过长，必须设置，比如连接不够用时，时间过长将是灾难性的
        clientHttpRequestFactory.setConnectionRequestTimeout(200);
        // 缓冲请求数据，默认值是true。通过POST或者PUT大量发送数据时，建议将此属性更改为false，以免耗尽内存。
        RestTemplate restTemplate = new RestTemplate();

        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));

        restTemplate.setRequestFactory(clientHttpRequestFactory);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

        return restTemplate;
    }


    public String formatURI(String uri, Map<String,Object> map) {
        boolean ext=uri.indexOf("$")>0;
        for (Map.Entry<String,Object> entry : map.entrySet()) {
            String regex = ext?"\\$":""+"\\{" + entry.getKey() + "\\}";
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(uri);
            uri = matcher.replaceAll(DataObject.getStringValue(entry.getValue(),"null"));
        }
        return uri;
    }



    /**
     * 同步预置代码表
     * @param catalogs
     */
    public void syncRuntimeDict(List<DictCatalog> catalogs){
        List<DictOption> options=new ArrayList<>();
        for(DictCatalog catalog : catalogs){
            if(!ObjectUtils.isEmpty(catalog.getOptions()))
                options.addAll(catalog.getOptions());
        }
        if(catalogs.size()>0){
            dictCatalogService.saveBatch(catalogs);
        }
        if(options.size()>0){
            optionService.saveBatch(options);
        }
    }
}
