package cd.casic.plugin.dataobject.pojo;


import cd.casic.plugin.PluginApplicationContextFactory;
import cd.casic.plugin.core.PluginContext;
import cd.casic.plugin.event.SpringPluginStartedEvent;
import cd.casic.plugin.event.SpringPluginStartingEvent;
import cd.casic.plugin.event.SpringPluginStoppingEvent;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.pf4j.Plugin;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import java.util.Optional;

/**
 * @Classname SpringPlugin
 * @Description TODO
 * @Date 2025/5/8 14:48
 * @Created by mianbin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@RequiredArgsConstructor
public class SpringPlugin extends Plugin {

    private ApplicationContext context;

    private Plugin delegate;

    private final PluginApplicationContextFactory contextFactory;

    private final PluginContext pluginContext;

    @Override
    public void start() {
        log.info("Preparing starting plugin {}", pluginContext.getName());
        var pluginId = pluginContext.getName();
        try {
            // initialize context
            this.context = contextFactory.create(pluginId);
            log.info("Application context {} for plugin {} is created", this.context, pluginId);
            Optional<Plugin> pluginOpt = context.getBeanProvider(Plugin.class)
                    .stream()
                    .findFirst();
            log.info("Before publishing plugin starting event for plugin {}", pluginId);
            context.publishEvent(new SpringPluginStartingEvent(this, this));
            log.info("After publishing plugin starting event for plugin {}", pluginId);
            pluginOpt.ifPresent(t -> {
                this.delegate = t;
                log.info("Starting {} for plugin {}", this.delegate, pluginId);
                this.delegate.start();
                log.info("Started {} for plugin {}", this.delegate, pluginId);
            });
            log.info("Before publishing plugin started event for plugin {}", pluginId);
            context.publishEvent(new SpringPluginStartedEvent(this, this));
            log.info("After publishing plugin started event for plugin {}", pluginId);
        } catch (Throwable t) {
            // try to stop plugin for cleaning resources if something went wrong
            log.error(
                    "Cleaning up plugin resources for plugin {} due to not being able to start plugin.",
                    pluginId);
            this.stop();
            // propagate exception to invoker.
            throw t;
        }
    }

    @Override
    public void stop() {
        try {
            Optional.ofNullable(context).ifPresent(v -> {
                log.info("Before publishing plugin stopping event for plugin {}",
                        pluginContext.getName());
                v.publishEvent(new SpringPluginStoppingEvent(this, this));
                log.info("After publishing plugin stopping event for plugin {}",
                        pluginContext.getName());
            });
            Optional.ofNullable(this.delegate).ifPresent(d -> {
                log.info("Stopping {} for plugin {}", this.delegate, pluginContext.getName());
                d.stop();
                log.info("Stopped {} for plugin {}", this.delegate, pluginContext.getName());
            });
        } finally {
            if (context instanceof ConfigurableApplicationContext configurableContext) {
                log.info("Closing plugin context for plugin {}", pluginContext.getName());
                configurableContext.close();
                log.info("Closed plugin context for plugin {}", pluginContext.getName());
            }
            log.info("Reset plugin context for plugin {}", pluginContext.getName());
            context = null;
        }
    }

    @Override
    public void delete() {
        if (delegate != null) {
            delegate.delete();
        }
        this.delegate = null;
    }

    public ApplicationContext getApplicationContext() {
        return context;
    }
}
