package cd.casic.plugin;

import cd.casic.plugin.event.PluginLoadedEvent;
import cd.casic.plugin.event.PluginUnloadedEvent;
import cd.casic.plugin.pf4j.OpsJarPluginRepository;
import cd.casic.plugin.pf4j.OpsPluginStatusProvider;
import cd.casic.plugin.pf4j.OpsPropertiesPluginDescriptorFinder;
import cd.casic.plugin.pf4j.OpsYamlPluginDescriptorFinder;
import cd.casic.plugin.register.StartPluginManagerHandler;
import cd.casic.plugin.utils.PluginsUtils;
import cd.casic.plugin.utils.SM4EncryptUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.pf4j.*;
import org.pf4j.util.FileUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @Author：mianbin
 * @Package：cd.casic.plugin
 * @Project：ops
 * @name：PluginManager
 * @Date：2024/03/18 15:03
 * @Filename：PluginManager
 * @description：插件核心管理类
 */
@Slf4j
@Component
public class PluginManager extends DefaultPluginManager implements PluginManagerService, ApplicationContextAware, PluginLifecycle {

    @Getter
    ApplicationContext applicationContext;

    @Getter
    private final PluginProperties pluginProperties;

    @Autowired
    private StartPluginManagerHandler startPluginManagerHandler;

    @Autowired
    private BufferedPluginBundleResource bufferedPluginBundleResource;

    @Autowired
    private FrontResourceProcessor frontResourceProcessor;

    public PluginManager(@Autowired PluginProperties pluginProperties) {
        this.pluginsRoots.add(Paths.get(pluginProperties.getPluginsRoot()));
        this.pluginProperties = pluginProperties;
        this.setRuntimeMode(pluginProperties.getRuntimeMode());
        super.initialize();
    }

    @Override
    protected void initialize() {
        // 这里不需要任何操作，只是覆盖父类的initialize方法
        // 如果不重写的话，在调用构造函数的时候会调用父类的无参构造函数，导致父类initialize方法被调用。
        // 父类initialize方法中会调用createPluginStatusProvider方法，该方法需要使用到OpsPluginSystemProperties，而此时还未被注入
    }


    @Override
    public PluginInfo install(Path path) throws Exception {

        // TODO 在install的时候生成插件目录结构
        /*
            工作目录
            │
            ├─plugins
            │  ├─example-mybatis-plugin@0.1 插件目录，id@version，存放jar、database、resource
            │  │  ├─ops-module-plugin-example-mybatis-plugin.jar 插件jar包，jar包命名不影响
            │  │  ├─database 存放sqlite数据库文件
            │  │  └─resource 存放静态资源
            │  └─example-redis-plugin@1.1.4
            │     ├─ops-module-plugin-example-redis-plugin.jar
            │     ├─database
            │     └─resource
         */

        String pluginId = null;
        try {
            this.beforeWork();
            pluginId = this.loadPlugin(path);
            super.startPlugin(pluginId);
            log.info("install plugin [{}] success", pluginId);
            return PluginCache.getPlugin(pluginId);
        } catch (Exception e) {
            log.error("安装插件出现异常:{}", e.getMessage());
            if (StringUtils.isNotBlank(pluginId)) {
                PluginCache.remove(pluginId);
            }
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public void installAfter(String pluginId) {
        try {
            super.stopPlugin(pluginId);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("install plugin after error:{}", e.getMessage());
        }
    }

    @Override
    public void unInstall(String pluginId, boolean isUpdate) throws Exception {
        // TODO unInstall的时候除掉插件资源
        log.info("准备卸载插件:" + pluginId);
        PluginInfo plugin = PluginCache.getPlugin(pluginId);
        plugin.clearApplicationContext();
        PluginCache.remove(pluginId);
        //删除插件
        PluginsUtils.forceDelete(plugin.getPluginWrapper().getPluginPath().toFile());
    }

    @Override
    public void initPlugins(List<PluginBean> plugins) throws Exception {
        // 初始化插件处理器
        startPluginManagerHandler.initialize();
        for (PluginBean plugin : plugins) {
            File file = new File(pluginsRoots.get(0) + File.separator + plugin.getPath());
            if (!file.exists()) {
                continue;
            }
            String pluginId = this.loadPlugin(file.toPath().toAbsolutePath());
            PluginInfo pluginInfo = PluginCache.getPlugin(pluginId);
            if (plugin.getStatus() == 1) {
                this.startPlugin(pluginInfo.getPluginId());
            }
        }
    }

    @Override
    public List<PluginWrapper> getInstallPlugins() {
        return getPlugins();
    }

    /**
     * 加载插件
     *
     * @param pluginPath 插件路径
     * @return String
     */
    @Override
    public String loadPlugin(Path pluginPath) {
        Optional.ofNullable(pluginPath)
                .filter(path -> Files.exists(path))
                .orElseThrow(() -> new IllegalArgumentException(String.format("plugin %s 不存在", pluginPath)));

        log.debug("Loading plugin from '{}'", pluginPath);

        PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
        PluginInfo pluginInfo = new PluginInfo(pluginWrapper, applicationContext);
        try {
            resolvePlugins();
            sendPluginEvenet(new PluginLoadedEvent(this, pluginInfo));
        } catch (Exception e) {
            log.error("加载插件出现异常:{}", e.getMessage());
            sendPluginEvenet(new PluginUnloadedEvent(this, pluginInfo));
        }
        return pluginWrapper.getDescriptor().getPluginId();
    }

    @Override
    public PluginWrapper loadPluginFromPath(Path pluginPath) {
        try {
            pluginPath = FileUtils.expandIfZip(pluginPath);
        } catch (Exception var3) {
            log.info(MessageFormat.format("路径:{0}的插件包解压缩失败，失败原因{1} ", pluginPath, var3));
            return null;
        }
        return super.loadPluginFromPath(pluginPath);
    }

    @Override
    public void loadPlugins() {
        log.debug("Lookup plugins in '{}'", pluginsRoots);
        // check for plugins roots
        if (pluginsRoots.isEmpty()) {
            log.warn("No plugins roots configured");
            return;
        }
        pluginsRoots.forEach(path -> {
            if (Files.notExists(path) || !Files.isDirectory(path)) {
                log.warn("No '{}' root", path);
            }
        });

        // get all plugin paths from repository
        List<Path> pluginPaths = pluginRepository.getPluginPaths();

        // check for no plugins
        if (pluginPaths.isEmpty()) {
            log.info("No plugins");
            return;
        }

        log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);

        // load plugins from plugin paths
        for (Path pluginPath : pluginPaths) {
            try {
                PluginWrapper pluginWrapper = this.loadPluginFromPath(pluginPath);
                if (ObjectUtils.isNotEmpty(pluginWrapper)) {
                    String license = pluginWrapper.getDescriptor().getLicense();
                    if (SM4EncryptUtil.checkLicense(pluginWrapper.getDescriptor().getLicense(), pluginWrapper.getPluginId())) {
                        PluginInfo plugin = new PluginInfo(pluginWrapper, applicationContext);
                        // startPluginManagerHandler.registry(plugin);
                        sendPluginEvenet(new PluginLoadedEvent(this, plugin));
                    }
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

        // resolve plugins
        try {
            resolvePlugins();
        } catch (PluginRuntimeException e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public boolean unloadPlugin(String pluginId) {
        if (pluginId == null) {
            throw new IllegalArgumentException(String.format("plugin %s 不存在", pluginId));
        }
        log.info("unloading plugin {}", pluginId);
        try {
            super.unloadPlugin(pluginId, true);
        } catch (Exception e) {
            log.info("卸载插件出现异常，{}", e.getMessage());
            return false;
        } finally {
            sendPluginEvenet(new PluginUnloadedEvent(this, PluginCache.getPlugin(pluginId)));
            System.gc();
        }
        return true;
    }


    @Override
    public void unloadPlugins() {
        for (PluginWrapper pluginWrapper : new ArrayList<>(resolvedPlugins)) {
            this.unloadPlugin(pluginWrapper.getPluginId());
        }
    }

    @Override
    public PluginState startPlugin(String pluginId) {
        PluginState pluginState = null;
        try {
            PluginInfo plugin = PluginCache.getPlugin(pluginId);
            if (plugin.getPluginWrapper().getPluginState().equals(PluginState.DISABLED)) return PluginState.STOPPED;

            if (ObjectUtils.isEmpty((plugin.getPluginApplicationContext()))) {
                plugin.setPluginApplicationContext(plugin.getContext());
                plugin.getPluginApplicationContext().setParent(applicationContext);
            }

            startPluginManagerHandler.registry(plugin);
            pluginState = super.startPlugin(pluginId);

            // todo 这里有uglify存在循环遍历的问题，后面改
            bufferedPluginBundleResource.getJsBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyJsBundle());
            bufferedPluginBundleResource.getCssBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyCssBundle());
//            bufferedPluginBundleResource.getHtmlBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyHtmlBundle());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("plugin start error : {}", e.getMessage());
        }
        return pluginState;
    }

    protected PluginState stopPlugin(String pluginId, boolean stopDependents) {
        checkPluginId(pluginId);

        PluginWrapper pluginWrapper = getPlugin(pluginId);
        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
        PluginState pluginState = pluginWrapper.getPluginState();
        if (PluginState.STOPPED == pluginState) {
            log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor));
            return PluginState.STOPPED;
        }

        // test for disabled plugin
        if (PluginState.DISABLED == pluginState) {
            // do nothing
            return pluginState;
        }

        if (stopDependents) {
            List<String> dependents = dependencyResolver.getDependents(pluginId);
            while (!dependents.isEmpty()) {
                String dependent = dependents.remove(0);
                stopPlugin(dependent, false);
                dependents.addAll(0, dependencyResolver.getDependents(dependent));
            }
        }

        log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor));
        pluginWrapper.getPlugin().stop();
        pluginWrapper.setPluginState(PluginState.STOPPED);
        startedPlugins.remove(pluginWrapper);

        try {
            // todo 没得到plugin
            PluginInfo plugin = PluginCache.getPlugin(pluginId);
            // todo 停止事件
            startPluginManagerHandler.unRegistry(plugin);
            plugin.clearApplicationContext();
        } catch (Exception e) {
            log.error("plugin stop error : {}", e.getMessage());
        }

        firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
        this.AfterWork();
        return pluginWrapper.getPluginState();
    }

    public void setRuntimeMode(RuntimeMode runtimeMode) {
        this.runtimeMode = runtimeMode;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    protected PluginDescriptorFinder createPluginDescriptorFinder() {
        return new CompoundPluginDescriptorFinder()
                .add(new OpsPropertiesPluginDescriptorFinder())
                .add(new OpsYamlPluginDescriptorFinder());
    }

    @Override
    protected PluginRepository createPluginRepository() {
//        return new CompoundPluginRepository()
//                .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment)
//                .add(new OpsJarPluginRepository(getPluginsRoots()), this::isNotDevelopment);
        // .add(new DefaultPluginRepository(getPluginsRoots()), this::isNotDevelopment);
        return new OpsJarPluginRepository(getPluginsRoots());
    }

    @Override
    protected PluginLoader createPluginLoader() {
        return new CompoundPluginLoader()
                .add(new JarPluginLoader(this))
                .add(new DefaultPluginLoader(this));
    }

    @Override
    public boolean isDevelopment() {
        return this.getRuntimeMode().equals(RuntimeMode.DEVELOPMENT);
    }

    @Override
    protected PluginStatusProvider createPluginStatusProvider() {
        if (OpsPluginStatusProvider.isPropertySet(pluginProperties)) {
            return new OpsPluginStatusProvider(pluginProperties);
        }
        return super.createPluginStatusProvider();
    }

    @Override
    public void beforeWork() {

    }

    @Override
    public void AfterWork() {

    }
}
