package com.friday.dynamic.core;

import com.alibaba.dubbo.config.ApplicationConfig;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

/**
 * @Author YaoYao
 * @Date Created In 15:55 2023/2/9
 * @Description
 * @Modified By
 */
@Slf4j
@Service
public class ModuleLoader implements ApplicationContextAware {
    @Setter
    private ApplicationContext applicationContext;
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Value("${dubbo.application.name}")
    private String applicationName;
    @Value("${dubbo.application.owner}")
    private String applicationOwner;
    @Value("${dubbo.application.logger}")
    private String applicationLogger;
    @Value("${spring.profiles.active}")
    private String env;
    
    @Autowired
    private EnvironmentLoader environmentLoader;

    /**
     * 加载模块
     */
    public Module load(Path jarPath, List<Path> dependencyJarPath) {
        log.info("Start to load module: {}", jarPath);
        
        try {
			environmentLoader.load(jarPath);
		} catch (Exception e) {
			log.error("load module properties error", e);
		}
        ModuleClassLoader moduleClassLoader;
        try {
            List<URL> dependencyUrls = dependencyJarPath.stream().map(path -> {
                try {
                    return path.toUri().toURL();
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
                return null;
            }).collect(Collectors.toList());
            moduleClassLoader = new ModuleClassLoader(applicationContext.getClassLoader(), jarPath.toUri().toURL(), dependencyUrls);
        } catch (MalformedURLException e) {
            throw new ModuleRuntimeException("create ModuleClassLoader exception", e);
        }
        List<ModuleConfig> moduleConfigList = new ArrayList<>();
        ServiceLoader.load(ModuleConfig.class, moduleClassLoader).forEach(moduleConfigList::add);
        if (moduleConfigList.size() != 1) {
            throw new ModuleRuntimeException("module config has and only has one");
        }
        ModuleConfig moduleConfig = moduleConfigList.get(0);
        applicationContext.getAutowireCapableBeanFactory().autowireBean(moduleConfig);
        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            // 把当前线程的ClassLoader切换成模块的
            Thread.currentThread().setContextClassLoader(moduleClassLoader);
            ModuleApplicationContext moduleApplicationContext = new ModuleApplicationContext();
            moduleApplicationContext.setParent(applicationContext);
            moduleApplicationContext.setClassLoader(moduleClassLoader);
            if(!"local".equals(env)){
                //加载依赖包
                moduleClassLoader.currentClassloaderAddUrl();
                //加载mapper
                moduleClassLoader.loadMapper();
                MapperLoader mapperLoader = new MapperLoader();
                Map<String, Object> mapperBeanMap = mapperLoader.refresh(sqlSessionFactory, moduleClassLoader.getXmlBytesMap());
                registerSingleton(moduleApplicationContext, mapperBeanMap);
            }
            //加载dubbo
            if(!moduleConfig.dubboConfigList().isEmpty()){
                moduleClassLoader.loadDubboService(moduleConfig.dubboConfigList());
                DubboLoader dubboLoader = new DubboLoader();
                dubboLoader.setApplicationConfig(this.createApplicationConfig());
                Map<String, Object> dubboBeanMap = dubboLoader.refresh(moduleConfig.dubboConfigList(), moduleClassLoader.getDubboServiceClassMap());
                registerSingleton(moduleApplicationContext, dubboBeanMap);
            }
            //加载POJO
            moduleApplicationContext.scan(moduleConfig.scanPackages().toArray(new String[0]));
            moduleApplicationContext.refresh();
            log.info("Load module success: name={}, version={}, jarPath={}", moduleConfig.name(), moduleConfig.version(), jarPath);
            return new Module(jarPath, moduleConfig, moduleApplicationContext, moduleClassLoader);
        } catch (Throwable e) {
            log.error(String.format("Load module exception, jarPath=%s", jarPath), e);
            CachedIntrospectionResults.clearClassLoader(moduleClassLoader);
            throw new ModuleRuntimeException("create ModuleApplicationContext exception", e);
        } finally {
            // 还原当前线程的ClassLoader
            Thread.currentThread().setContextClassLoader(currentClassLoader);
        }
    }

    private ApplicationConfig createApplicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName(applicationName);
        applicationConfig.setOwner(applicationOwner);
        applicationConfig.setLogger(applicationLogger);
        return applicationConfig;
    }

    //将各种资源放入spring容器
    private void registerSingleton(ApplicationContext applicationContext, Map<String, Object> beanMaps){
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        if(beanMaps != null && !beanMaps.isEmpty()) {
            beanMaps.forEach((beanName,obj) ->{
                if(defaultListableBeanFactory.containsSingleton(beanName)) {
                    defaultListableBeanFactory.destroySingleton(beanName);
                }
                defaultListableBeanFactory.registerSingleton(beanName,obj);
            });
        }
    }
}
