package org.jeecg.loader;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.config.GatewayRoutersConfiguration;
import org.jeecg.config.RouterDataType;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 动态路由加载器
 *
 * @author : zyf
 * @date :2020-11-10
 */
@Slf4j
@Component
@DependsOn({"gatewayRoutersConfiguration"})
public class DynamicRouteLoader implements ApplicationEventPublisherAware {


    private ApplicationEventPublisher publisher;

    private InMemoryRouteDefinitionRepository repository;

    private DynamicRouteService dynamicRouteService;

    private ConfigService configService;

    private RedisUtil redisUtil;


    public DynamicRouteLoader(InMemoryRouteDefinitionRepository repository, DynamicRouteService dynamicRouteService, RedisUtil redisUtil) {

        this.repository = repository;
        this.dynamicRouteService = dynamicRouteService;
        this.redisUtil = redisUtil;
    }

    @PostConstruct
    public void init() {
        String dataType = GatewayRoutersConfiguration.DATA_TYPE;
        log.info("初始化路由，dataType："+ dataType);
        if (RouterDataType.nacos.toString().endsWith(dataType)) {
            loadRoutesByNacos();
        }
        //从数据库加载路由
        if (RouterDataType.database.toString().endsWith(dataType)) {
            loadRoutesByRedis();
        }
    }


    /**
     * 刷新路由
     *
     * @return
     */
    public Mono<Void> refresh() {
        String dataType = GatewayRoutersConfiguration.DATA_TYPE;
        if (!RouterDataType.yml.toString().endsWith(dataType)) {
            this.init();
        }
        return Mono.empty();
    }


    /**
     * 从nacos中读取路由配置
     *
     * @return
     */
    private void loadRoutesByNacos() {
        List<RouteDefinition> routes = Lists.newArrayList();
        configService = createConfigService();
        if (configService == null) {
            log.warn("initConfigService fail");
        }
        try {
            String configInfo = configService.getConfig(GatewayRoutersConfiguration.DATA_ID, GatewayRoutersConfiguration.ROUTE_GROUP, GatewayRoutersConfiguration.DEFAULT_TIMEOUT);
            if (StringUtils.isNotBlank(configInfo)) {
                log.info("获取网关当前配置:\r\n{}", configInfo);
                routes = JSON.parseArray(configInfo, RouteDefinition.class);
            }
        } catch (NacosException e) {
            log.error("初始化网关路由时发生错误", e);
            e.printStackTrace();
        }
        for (RouteDefinition definition : routes) {
            log.info("update route : {}", definition.toString());
            dynamicRouteService.add(definition);
        }
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        dynamicRouteByNacosListener(GatewayRoutersConfiguration.DATA_ID, GatewayRoutersConfiguration.ROUTE_GROUP);
    }


    /**
     * 从redis中读取路由配置
     *
     * @return
     */
    private void loadRoutesByRedis() {
        List<MyRouteDefinition> routes = Lists.newArrayList();
        configService = createConfigService();
        if (configService == null) {
            log.warn("initConfigService fail");
        }
        Object configInfo = redisUtil.get(CacheConstant.GATEWAY_ROUTES);
        if (ObjectUtil.isNotEmpty(configInfo)) {
            log.info("获取网关当前配置:\r\n{}", configInfo);
            JSONArray array = JSON.parseArray(configInfo.toString());
            try {
                routes = getRoutesByJson(array);
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }
        for (MyRouteDefinition definition : routes) {
            log.info("update route : {}", definition.toString());
            Integer status=definition.getStatus();
            if(status.equals(0)){
                dynamicRouteService.delete(definition.getId());
            }else{
                dynamicRouteService.add(definition);
            }
        }
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * redis中的信息需要处理下 转成RouteDefinition对象
     * - id: login
     * uri: lb://cloud-jeecg-system
     * predicates:
     * - Path=/jeecg-boot/sys/**,
     *
     * @param array
     * @return
     */

    public static List<MyRouteDefinition> getRoutesByJson(JSONArray array) throws URISyntaxException {
        List<MyRouteDefinition> ls = new ArrayList<>();
        for (int i = 0; i < array.size(); i++) {
            JSONObject obj = array.getJSONObject(i);
            MyRouteDefinition route = new MyRouteDefinition();
            route.setId(obj.getString("routerId"));
            route.setStatus(obj.getInteger("status"));
            Object uri = obj.get("uri");
            if (uri == null) {
                route.setUri(new URI("lb://" + obj.getString("name")));
            } else {
                route.setUri(new URI(obj.getString("uri")));
            }
            Object predicates = obj.get("predicates");
            if (predicates != null) {
                JSONArray list = JSON.parseArray(predicates.toString());
                List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
                for (Object map : list) {
                    JSONObject json = (JSONObject) map;
                    PredicateDefinition predicateDefinition = new PredicateDefinition();
                    predicateDefinition.setName(json.getString("name"));
                    JSONArray jsonArray = json.getJSONArray("args");
                    for (int j = 0; j < jsonArray.size(); j++) {
                        predicateDefinition.addArg("_genkey" + j, jsonArray.get(j).toString());
                    }
                    predicateDefinitionList.add(predicateDefinition);
                }
                route.setPredicates(predicateDefinitionList);
            }

            Object filters = obj.get("filters");
            if (filters != null) {
                JSONArray list = JSON.parseArray(filters.toString());
                List<FilterDefinition> filterDefinitionList = new ArrayList<>();
                if (ObjectUtil.isNotEmpty(list)) {
                    for (Object map : list) {
                        JSONObject json = (JSONObject) map;
                        JSONArray jsonArray = json.getJSONArray("args");
                        String name = json.getString("name");
                        FilterDefinition filterDefinition = new FilterDefinition();
                        for (Object o : jsonArray) {
                            JSONObject params = (JSONObject) o;
                            filterDefinition.addArg(params.getString("key"), params.get("value").toString());
                        }
                        filterDefinition.setName(name);
                        filterDefinitionList.add(filterDefinition);
                    }
                    route.setFilters(filterDefinitionList);
                }
            }
            ls.add(route);
        }
        return ls;
    }


//    private void loadRoutesByDataBase() {
//        List<GatewayRouteVo> routeList = jdbcTemplate.query(SELECT_ROUTES, new RowMapper<GatewayRouteVo>() {
//            @Override
//            public GatewayRouteVo mapRow(ResultSet rs, int i) throws SQLException {
//                GatewayRouteVo result = new GatewayRouteVo();
//                result.setId(rs.getString("id"));
//                result.setName(rs.getString("name"));
//                result.setUri(rs.getString("uri"));
//                result.setStatus(rs.getInt("status"));
//                result.setRetryable(rs.getInt("retryable"));
//                result.setPredicates(rs.getString("predicates"));
//                result.setStripPrefix(rs.getInt("strip_prefix"));
//                result.setPersist(rs.getInt("persist"));
//                return result;
//            }
//        });
//        if (ObjectUtil.isNotEmpty(routeList)) {
//            // 加载路由
//            routeList.forEach(route -> {
//                RouteDefinition definition = new RouteDefinition();
//                List<PredicateDefinition> predicatesList = Lists.newArrayList();
//                List<FilterDefinition> filtersList = Lists.newArrayList();
//                definition.setId(route.getId());
//                String predicates = route.getPredicates();
//                String filters = route.getFilters();
//                if (StringUtils.isNotEmpty(predicates)) {
//                    predicatesList = JSON.parseArray(predicates, PredicateDefinition.class);
//                    definition.setPredicates(predicatesList);
//                }
//                if (StringUtils.isNotEmpty(filters)) {
//                    filtersList = JSON.parseArray(filters, FilterDefinition.class);
//                    definition.setFilters(filtersList);
//                }
//                URI uri = UriComponentsBuilder.fromUriString(route.getUri()).build().toUri();
//                definition.setUri(uri);
//                this.repository.save(Mono.just(definition)).subscribe();
//            });
//            log.info("加载路由:{}==============", routeList.size());
//            Mono.empty();
//        }
//    }


    /**
     * 监听Nacos下发的动态路由配置
     *
     * @param dataId
     * @param group
     */
    public void dynamicRouteByNacosListener(String dataId, String group) {
        try {
            configService.addListener(dataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("进行网关更新:\n\r{}", configInfo);
                    List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                    for (RouteDefinition definition : definitionList) {
                        log.info("update route : {}", definition.toString());
                        dynamicRouteService.update(definition);
                    }
                }

                @Override
                public Executor getExecutor() {
                    log.info("getExecutor\n\r");
                    return null;
                }
            });
        } catch (Exception e) {
            log.error("从nacos接收动态路由配置出错!!!", e);
        }
    }

    /**
     * 创建ConfigService
     *
     * @return
     */
    private ConfigService createConfigService() {
        try {
            Properties properties = new Properties();
            properties.setProperty("serverAddr", GatewayRoutersConfiguration.SERVER_ADDR);
            properties.setProperty("namespace", GatewayRoutersConfiguration.NAMESPACE);
            properties.setProperty("username",GatewayRoutersConfiguration.USERNAME);
            properties.setProperty("password",GatewayRoutersConfiguration.PASSWORD);
            return configService = NacosFactory.createConfigService(properties);
        } catch (Exception e) {
            log.error("创建ConfigService异常", e);
            return null;
        }
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}
