package cd.casic.plugin.core;

import cd.casic.plugin.PluginApplicationContextFactory;
import cd.casic.plugin.PluginRouterFunctionRegistry;
import cd.casic.plugin.config.PluginProperties;
import cd.casic.plugin.dataobject.pojo.SpringPlugin;
import cd.casic.plugin.event.*;
import cd.casic.plugin.function.FinderRegistry;
import cd.casic.plugin.function.WebSocketEndpointManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @description: TODO
 * @author: mianbin
 * @date: 2025/5/12 23:48
 * @version: 1.0
 */
@Slf4j
@RequiredArgsConstructor
public class DefaultPluginApplicationContextFactory implements PluginApplicationContextFactory {

    private final SpringPluginManager pluginManager;

    @Override
    public ApplicationContext create(String pluginId) {
        return null;
    }

    private static class FinderManager {

        private final String pluginId;

        private final FinderRegistry finderRegistry;

        private FinderManager(String pluginId, FinderRegistry finderRegistry) {
            this.pluginId = pluginId;
            this.finderRegistry = finderRegistry;
        }

        @EventListener
        public void onApplicationEvent(ContextClosedEvent ignored) {
            this.finderRegistry.unregister(this.pluginId);
        }

        @EventListener
        public void onApplicationEvent(ContextRefreshedEvent event) {
            this.finderRegistry.register(this.pluginId, event.getApplicationContext());
        }
    }

    private static class PluginWebSocketEndpointManager {

        private final WebSocketEndpointManager manager;

        private List<Object> endpoints;

        private PluginWebSocketEndpointManager(WebSocketEndpointManager manager) {
            this.manager = manager;
        }

        @EventListener
        public void onApplicationEvent(ContextRefreshedEvent event) {
            var context = event.getApplicationContext();
            this.endpoints = context.getBeanProvider(Object.class)
                    .orderedStream()
                    .toList();
            manager.register(this.endpoints);
        }

        @EventListener
        public void onApplicationEvent(ContextClosedEvent ignored) {
            manager.unregister(this.endpoints);
        }
    }

    private static class PluginRouterFunctionManager {

        private final PluginRouterFunctionRegistry routerFunctionRegistry;

        private Collection<RouterFunction<ServerResponse>> routerFunctions;

        private PluginRouterFunctionManager(PluginRouterFunctionRegistry routerFunctionRegistry) {
            this.routerFunctionRegistry = routerFunctionRegistry;
        }

        @EventListener
        public void onApplicationEvent(ContextClosedEvent ignored) {
            if (routerFunctions != null) {
                routerFunctionRegistry.unregister(routerFunctions);
            }
        }

        @EventListener
        public void onApplicationEvent(ContextRefreshedEvent event) {
            var routerFunctions = event.getApplicationContext()
                    .<RouterFunction<ServerResponse>>getBeanProvider(
                            ResolvableType.forClassWithGenerics(RouterFunction.class, ServerResponse.class)
                    )
                    .orderedStream()
                    .toList();
            routerFunctionRegistry.register(routerFunctions);
            this.routerFunctions = routerFunctions;
        }
    }


    private static class PluginHandlerMappingManager {
        private final String pluginId;

        private final PluginRequestMappingHandlerMapping handlerMapping;

        private PluginHandlerMappingManager(String pluginId,
                                            PluginRequestMappingHandlerMapping handlerMapping) {
            this.pluginId = pluginId;
            this.handlerMapping = handlerMapping;
        }

        @EventListener
        public void onApplicationEvent(ContextRefreshedEvent event) {
            var context = event.getApplicationContext();
            context.getBeansWithAnnotation(Controller.class)
                    .values()
                    .forEach(controller ->
                            handlerMapping.registerHandlerMethods(this.pluginId, controller)
                    );
        }

        @EventListener
        public void onApplicationEvent(ContextClosedEvent ignored) {
            handlerMapping.unregister(this.pluginId);
        }
    }

    private class SpringPluginStoppedEventAdapter
            implements ApplicationListener<ContextClosedEvent> {

        private final String pluginId;

        private SpringPluginStoppedEventAdapter(String pluginId) {
            this.pluginId = pluginId;
        }

        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            var plugin = pluginManager.getPlugin(pluginId).getPlugin();
            if (plugin instanceof SpringPlugin springPlugin) {
                event.getApplicationContext()
                        .publishEvent(new SpringPluginStoppedEvent(this, springPlugin));
            }
        }
    }

    private class HaloPluginEventBridge {

        @EventListener
        public void onApplicationEvent(SpringPluginStartedEvent event) {
            var pluginContext = event.getSpringPlugin().getPluginContext();
            var pluginWrapper = pluginManager.getPlugin(pluginContext.getName());

            pluginManager.getRootContext()
                    .publishEvent(new OpsPluginStartedEvent(this, pluginWrapper));
        }

        @EventListener
        public void onApplicationEvent(SpringPluginStoppingEvent event) {
            var pluginContext = event.getSpringPlugin().getPluginContext();
            var pluginWrapper = pluginManager.getPlugin(pluginContext.getName());
            pluginManager.getRootContext()
                    .publishEvent(new OpsPluginBeforeStopEvent(this, pluginWrapper));
        }

        @EventListener
        public void onApplicationEvent(SpringPluginStoppedEvent event) {
            var pluginContext = event.getSpringPlugin().getPluginContext();
            var pluginWrapper = pluginManager.getPlugin(pluginContext.getName());
            pluginManager.getRootContext()
                    .publishEvent(new OpsPluginStoppedEvent(this, pluginWrapper));
        }

    }

    private List<PropertySource<?>> resolvePropertySources(String pluginId,
                                                           ResourceLoader resourceLoader) {
        var opsProperties = pluginManager.getRootContext()
                .getBeanProvider(PluginProperties.class)
                .getIfAvailable();
        if (opsProperties == null) {
            return List.of();
        }

        var propertySourceLoader = new YamlPropertySourceLoader();
        var propertySources = new ArrayList<PropertySource<?>>();
        return propertySources;
    }

    private List<PropertySource<?>> loadPropertySources(String propertySourceName,
                                                        Resource resource,
                                                        PropertySourceLoader propertySourceLoader) {
        if (log.isDebugEnabled()) {
            log.debug("Loading property sources from {}", resource);
        }
        if (!resource.exists()) {
            return List.of();
        }
        try {
            return propertySourceLoader.load(propertySourceName, resource);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load property sources from " + resource, e);
        }
    }
}
