package cd.casic.plugin.core;

import cd.casic.plugin.SpringPluginFactory;
import cd.casic.plugin.YamlPluginDescriptorFinder;
import cd.casic.plugin.config.PluginProperties;
import cd.casic.plugin.dataobject.pojo.SpringPlugin;
import cd.casic.plugin.event.PluginStartedEvent;
import cd.casic.plugin.function.PluginGetter;
import cd.casic.plugin.function.PluginsRootGetter;
import cd.casic.plugin.function.SystemVersionSupplier;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.data.util.Lazy;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Stack;

/**
 * @description: manager plugin lifecycle
 * @author: mianbin
 * @date: 2025/5/12 15:36
 * @version: 1.0
 */
@Slf4j
@RequiredArgsConstructor
public class OpsPluginManager extends DefaultPluginManager implements SpringPluginManager, InitializingBean {

    private final ApplicationContext rootContext;
    private Lazy<ApplicationContext> sharedContext;
    private final PluginProperties pluginProperties;
    private final PluginsRootGetter pluginsRootGetter;
    private final SystemVersionSupplier systemVersionSupplier;

    @Override
    protected void initialize() {
        //super#initialize会最早初始化，留空
    }

    @Override
    protected ExtensionFactory createExtensionFactory() {
        return new SpringExtensionFactory(this);
    }

    @Override
    protected ExtensionFinder createExtensionFinder() {
        var finder = new SpringComponentsFinder(this);
        addPluginStateListener(finder);
        return finder;
    }

    @Override
    protected PluginFactory createPluginFactory() {
        var contextFactory = new DefaultPluginApplicationContextFactory(this);
        var pluginGetter = rootContext.getBean(PluginGetter.class);
        return new SpringPluginFactory(contextFactory, pluginGetter);
    }

    @Override
    protected PluginDescriptorFinder createPluginDescriptorFinder() {
        return new YamlPluginDescriptorFinder();
    }

    @Override
    protected PluginLoader createPluginLoader() {
        var compoundLoader = new CompoundPluginLoader();
        compoundLoader.add(new DevPluginLoader(this, this.pluginProperties), this::isDevelopment);
        compoundLoader.add(new JarPluginLoader(this));
        return compoundLoader;
    }

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

    @Override
    protected PluginRepository createPluginRepository() {
        var developmentPluginRepository =
                new DefaultDevelopmentPluginRepository(getPluginsRoots());
        String restPathPrefix = pluginProperties.getRestPathPrefix();
        Path path = Paths.get(restPathPrefix);
        developmentPluginRepository
                .setFixedPaths(List.of(path));
        return new CompoundPluginRepository()
                .add(developmentPluginRepository, this::isDevelopment)
                .add(new JarPluginRepository(getPluginsRoots()))
                .add(new DefaultPluginRepository(getPluginsRoots()));
    }

    @Override
    protected List<Path> createPluginsRoot() {
        String s = pluginsRootGetter.get();
        return List.of();
    }

    @Override
    public void startPlugins() {
        throw new UnsupportedOperationException(
                "don't do this operation,don't don't start all plugins"
        );
    }

    @Override
    public void stopPlugins() {
        throw new UnsupportedOperationException(
                "don't do this operation.don't don't stop all plugins"
        );
    }

    @Override
    public ApplicationContext getRootContext() {
        return rootContext;
    }

    @Override
    public ApplicationContext getSharedContext() {
        return sharedContext.get();
    }

    @Override
    public List<PluginWrapper> getDependents(String pluginId) {
        if (Objects.isNull(getPlugin(pluginId))) {
            return List.of();
        }
        var dependents = new ArrayList<PluginWrapper>();
        var stack = new Stack<String>();
        // 依赖管理
        dependencyResolver.getDependents(pluginId).forEach(stack::push);
        while (!stack.isEmpty()) {
            var dependent = stack.pop();
            var pluginWrapper = getPlugin(dependent);
            if (pluginWrapper != null) {
                dependents.add(pluginWrapper);
                dependencyResolver.getDependents(dependent).forEach(stack::push);
            }
        }
        return dependents;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        super.runtimeMode = pluginProperties.getRuntimeMode();
        this.sharedContext = Lazy.of(() -> SharedApplicationContextFactory.create(rootContext));
        setExactVersionAllowed(pluginProperties.isEnable());
        setSystemVersion(systemVersionSupplier.get().toStableVersion().toString());
        super.initialize();
        // the listener must be after the super#initialize
        addPluginStateListener(new PluginStartedListener());
    }


    /**
     * Listener for plugin started event.
     *
     * @author johnniang
     * @since 2.17.0
     */
    private static class PluginStartedListener implements PluginStateListener {

        @Override
        public void pluginStateChanged(PluginStateEvent event) {
            if (PluginState.STARTED.equals(event.getPluginState())) {
                var plugin = event.getPlugin().getPlugin();
                if (plugin instanceof SpringPlugin springPlugin) {
                    try {
                        springPlugin.getApplicationContext()
                                .publishEvent(new PluginStartedEvent(this));
                    } catch (Throwable t) {
                        var pluginId = event.getPlugin().getPluginId();
                        log.warn("Error while publishing plugin started event for plugin {}",
                                pluginId, t);
                    }
                }
            }
        }
    }
}
