package de.assecutor.votianlt.messaging.plugin;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * Manager for messaging plugins.
 * Handles plugin lifecycle, registration, and delegation.
 */
@Component
@Slf4j
public class PluginManager {

    private MessagingPlugin activePlugin;
    private final List<ConnectionStateEvent> connectionHistory = new ArrayList<>();
    private final List<PluginStateListener> stateListeners = new ArrayList<>();

    /**
     * Initialize and activate a plugin.
     *
     * @param plugin Plugin to activate
     * @param config Plugin configuration
     * @throws PluginException if initialization fails
     */
    public void activatePlugin(MessagingPlugin plugin, PluginConfig config) throws PluginException {
        log.info("[PluginManager] Activating plugin: {}", plugin.getPluginName());

        // Shutdown existing plugin if any
        if (activePlugin != null) {
            log.info("[PluginManager] Shutting down existing plugin: {}", activePlugin.getPluginName());
            try {
                activePlugin.exit();
            } catch (Exception e) {
                log.error("[PluginManager] Error shutting down existing plugin: {}", e.getMessage(), e);
            }
        }

        // Set connection listener
        plugin.setConnectionListener(event -> {
            String previousState = event.getPreviousState() != null
                    ? event.getPreviousState().toString()
                    : "NONE";
            log.info("[PluginManager] Connection state changed: {} -> {}",
                    previousState, event.getState());
            connectionHistory.add(event);
            notifyStateListeners(event);
        });

        // Initialize plugin
        plugin.init(config);
        activePlugin = plugin;

        log.info("[PluginManager] Plugin activated: {} v{}", 
                plugin.getPluginName(), plugin.getPluginVersion());
    }

    /**
     * Get the currently active plugin.
     *
     * @return Active plugin or empty if none
     */
    public Optional<MessagingPlugin> getActivePlugin() {
        return Optional.ofNullable(activePlugin);
    }

    /**
     * Send a message to a specific client via the active plugin.
     *
     * @param clientId    Target client identifier
     * @param messageType Type of message (e.g., "jobs", "message", "auth", "task")
     * @param payload     Message payload
     * @param options     Send options
     * @return CompletableFuture that completes when message is sent
     * @throws PluginException if no plugin is active or sending fails
     */
    public CompletableFuture<Void> sendToClient(String clientId, String messageType, byte[] payload, SendOptions options) throws PluginException {
        if (activePlugin == null) {
            return CompletableFuture.failedFuture(new PluginException("No active plugin"));
        }

        if (!activePlugin.isConnected()) {
            return CompletableFuture.failedFuture(new PluginException("Plugin is not connected"));
        }

        return activePlugin.sendToClient(clientId, messageType, payload, options);
    }

    /**
     * Send an acknowledgment to a specific client via the active plugin.
     *
     * @param clientId  Target client identifier
     * @param messageId Message ID being acknowledged
     * @param payload   ACK payload
     * @param options   Send options
     * @return CompletableFuture that completes when ACK is sent
     * @throws PluginException if no plugin is active or sending fails
     */
    public CompletableFuture<Void> sendAckToClient(String clientId, String messageId, byte[] payload, SendOptions options) throws PluginException {
        if (activePlugin == null) {
            return CompletableFuture.failedFuture(new PluginException("No active plugin"));
        }

        if (!activePlugin.isConnected()) {
            return CompletableFuture.failedFuture(new PluginException("Plugin is not connected"));
        }

        return activePlugin.sendAckToClient(clientId, messageId, payload, options);
    }

    /**
     * Register a handler for incoming messages of a specific type from clients.
     *
     * @param messageType Type of message to handle
     * @param handler     Message handler
     * @throws PluginException if no plugin is active or registration fails
     */
    public void registerMessageHandler(String messageType, MessagingPlugin.ClientMessageHandler handler) throws PluginException {
        if (activePlugin == null) {
            throw new PluginException("No active plugin");
        }

        activePlugin.registerMessageHandler(messageType, handler);
    }

    /**
     * Register a handler for incoming acknowledgments from clients.
     *
     * @param handler ACK handler
     * @throws PluginException if no plugin is active or registration fails
     */
    public void registerAckHandler(MessagingPlugin.AckHandler handler) throws PluginException {
        if (activePlugin == null) {
            throw new PluginException("No active plugin");
        }

        activePlugin.registerAckHandler(handler);
    }

    /**
     * Check if the active plugin is connected.
     *
     * @return true if connected, false otherwise
     */
    public boolean isConnected() {
        return activePlugin != null && activePlugin.isConnected();
    }

    /**
     * Get metadata of the active plugin.
     *
     * @return Plugin metadata or empty if no plugin is active
     */
    public Optional<PluginMetadata> getActivePluginMetadata() {
        return Optional.ofNullable(activePlugin).map(MessagingPlugin::getMetadata);
    }

    /**
     * Get connection history.
     *
     * @return List of connection state events
     */
    public List<ConnectionStateEvent> getConnectionHistory() {
        return new ArrayList<>(connectionHistory);
    }

    /**
     * Get the last connection state event.
     *
     * @return Last connection state event or empty if none
     */
    public Optional<ConnectionStateEvent> getLastConnectionState() {
        if (connectionHistory.isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(connectionHistory.get(connectionHistory.size() - 1));
    }

    /**
     * Add a plugin state listener.
     *
     * @param listener State listener
     */
    public void addStateListener(PluginStateListener listener) {
        stateListeners.add(listener);
    }

    /**
     * Remove a plugin state listener.
     *
     * @param listener State listener
     */
    public void removeStateListener(PluginStateListener listener) {
        stateListeners.remove(listener);
    }

    /**
     * Notify all state listeners of a connection state change.
     *
     * @param event Connection state event
     */
    private void notifyStateListeners(ConnectionStateEvent event) {
        for (PluginStateListener listener : stateListeners) {
            try {
                listener.onConnectionStateChanged(event);
            } catch (Exception e) {
                log.error("[PluginManager] Error in state listener: {}", e.getMessage(), e);
            }
        }
    }

    /**
     * Shutdown the plugin manager and active plugin.
     */
    @PreDestroy
    public void shutdown() {
        log.info("[PluginManager] Shutting down plugin manager");

        if (activePlugin != null) {
            try {
                activePlugin.exit();
                log.info("[PluginManager] Active plugin shut down successfully");
            } catch (Exception e) {
                log.error("[PluginManager] Error shutting down active plugin: {}", e.getMessage(), e);
            }
            activePlugin = null;
        }

        stateListeners.clear();
        connectionHistory.clear();
    }

    /**
     * Listener interface for plugin state changes.
     */
    @FunctionalInterface
    public interface PluginStateListener {
        /**
         * Called when plugin connection state changes.
         *
         * @param event Connection state event
         */
        void onConnectionStateChanged(ConnectionStateEvent event);
    }
}

