package de.assecutor.votianlt.messaging.config;

import de.assecutor.votianlt.controller.MessageController;
import de.assecutor.votianlt.dto.AppLoginRequest;
import de.assecutor.votianlt.messaging.delivery.MessageDeliveryService;
import de.assecutor.votianlt.messaging.model.AcknowledgmentMessage;
import de.assecutor.votianlt.messaging.model.MessageEnvelope;
import de.assecutor.votianlt.messaging.plugin.*;
import de.assecutor.votianlt.messaging.plugin.mqtt.MqttMessagingPlugin;
import de.assecutor.votianlt.service.ClientConnectionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * Configuration for the plugin-based messaging system.
 * Initializes the selected plugin and sets up message routing.
 */
@Configuration
@Slf4j
public class PluginMessagingConfig {

    @Value("${app.messaging.plugin.type:mqtt}")
    private String pluginType;

    @Value("${app.messaging.plugin.mqtt.broker.host:mqtt-2.assecutor.de}")
    private String mqttBrokerHost;

    @Value("${app.messaging.plugin.mqtt.broker.port:1883}")
    private int mqttBrokerPort;

    @Value("${app.messaging.plugin.mqtt.username:app}")
    private String mqttUsername;

    @Value("${app.messaging.plugin.mqtt.password:apppwd}")
    private String mqttPassword;

    @Value("${app.messaging.plugin.mqtt.client.id:votianlt-server}")
    private String mqttClientId;

    private final PluginManager pluginManager;
    private final ObjectMapper objectMapper;

    public PluginMessagingConfig(PluginManager pluginManager, ObjectMapper objectMapper) {
        this.pluginManager = pluginManager;
        this.objectMapper = objectMapper;
    }

    /**
     * Initialize the messaging plugin after application startup.
     * This method is called after all beans are created, so we can safely access MessageDeliveryService.
     */
    @EventListener(ApplicationReadyEvent.class)
    public void initializePlugin(ApplicationReadyEvent event) {
        log.info("[PluginMessagingConfig] Initializing messaging plugin: {}", pluginType);

        try {
            MessagingPlugin plugin = createPlugin(pluginType);
            PluginConfig config = createPluginConfig(pluginType);

            // Get beans from context (after all beans are created)
            MessageDeliveryService deliveryService = event.getApplicationContext().getBean(MessageDeliveryService.class);
            MessageController messageController = event.getApplicationContext().getBean(MessageController.class);
            ClientConnectionService clientConnectionService = event.getApplicationContext().getBean(ClientConnectionService.class);

            // Set up a listener to subscribe when connected
            log.info("[PluginMessagingConfig] Adding state listener");
            pluginManager.addStateListener(stateEvent -> {
                log.info("[PluginMessagingConfig] State event received: state={}, isConnected={}",
                        stateEvent.getState(), stateEvent.isConnected());
                if (stateEvent.isConnected()) {
                    log.info("[PluginMessagingConfig] Plugin connected, setting up subscriptions");
                    try {
                        setupSubscriptions(deliveryService, messageController, clientConnectionService);
                        log.info("[PluginMessagingConfig] Subscriptions setup completed");
                    } catch (Exception e) {
                        log.error("[PluginMessagingConfig] Error setting up subscriptions: {}", e.getMessage(), e);
                    }
                } else {
                    log.debug("[PluginMessagingConfig] Plugin not yet connected, waiting...");
                }
            });
            log.info("[PluginMessagingConfig] State listener added");

            // Activate plugin (this will trigger connection and eventually the listener above)
            pluginManager.activatePlugin(plugin, config);

            log.info("[PluginMessagingConfig] Plugin activation initiated, subscriptions will be set up when connected");

        } catch (Exception e) {
            log.error("[PluginMessagingConfig] Failed to initialize plugin: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to initialize messaging plugin", e);
        }
    }

    /**
     * Create a plugin instance based on the plugin type.
     */
    private MessagingPlugin createPlugin(String type) {
        return switch (type.toLowerCase()) {
            case "mqtt" -> new MqttMessagingPlugin();
            // Add more plugin types here in the future
            // case "websocket" -> new WebSocketMessagingPlugin();
            // case "grpc" -> new GrpcMessagingPlugin();
            default -> throw new IllegalArgumentException("Unknown plugin type: " + type);
        };
    }

    /**
     * Create plugin configuration based on the plugin type.
     */
    private PluginConfig createPluginConfig(String type) {
        PluginConfig config = new PluginConfig();

        switch (type.toLowerCase()) {
            case "mqtt" -> {
                config.setProperty("broker.host", mqttBrokerHost);
                config.setProperty("broker.port", mqttBrokerPort);
                config.setProperty("username", mqttUsername);
                config.setProperty("password", mqttPassword);
                config.setProperty("client.id", mqttClientId);
                config.setProperty("auto.reconnect", true);
                config.setProperty("clean.start", true);
            }
            // Add more plugin configurations here
            default -> throw new IllegalArgumentException("Unknown plugin type: " + type);
        }

        return config;
    }

    /**
     * Setup message subscriptions using the new plugin API.
     */
    private void setupSubscriptions(MessageDeliveryService deliveryService,
                                    MessageController messageController,
                                    ClientConnectionService clientConnectionService) {
        log.info("[PluginMessagingConfig] Setting up message subscriptions");

        try {
            // Register ACK handler
            pluginManager.registerAckHandler((messageId, payload) -> {
                try {
                    String json = new String(payload, StandardCharsets.UTF_8);
                    log.info("[PluginMessagingConfig] Received ACK JSON: {}", json);

                    // ACK messages are wrapped in MessageEnvelope
                    MessageEnvelope envelope = objectMapper.readValue(json, MessageEnvelope.class);
                    AcknowledgmentMessage ack = objectMapper.convertValue(envelope.getPayload(), AcknowledgmentMessage.class);
                    deliveryService.handleAcknowledgment(ack);
                } catch (Exception e) {
                    log.error("[PluginMessagingConfig] Error handling ACK message: {}", e.getMessage(), e);
                }
            });

            // Register message handlers for different message types
            String[] messageTypes = {
                "task_completed",
                "jobs/assigned",
                "message",
                "login",
                "pong"
            };

            for (String messageType : messageTypes) {
                pluginManager.registerMessageHandler(messageType, (clientId, payload) ->
                    handleEnvelopedMessage(clientId, payload, deliveryService, messageController, clientConnectionService));
            }

            log.info("[PluginMessagingConfig] Message subscriptions initialized");

        } catch (Exception e) {
            log.error("[PluginMessagingConfig] Error setting up subscriptions: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to setup subscriptions", e);
        }
    }

    /**
     * Handle incoming enveloped message.
     * Supports both new envelope format and legacy format for backwards compatibility.
     */
    private void handleEnvelopedMessage(String clientId, byte[] payload, MessageDeliveryService deliveryService,
                                        MessageController messageController, ClientConnectionService clientConnectionService) {
        try {
            String json = new String(payload, StandardCharsets.UTF_8);
            log.info("[PluginMessagingConfig] Received JSON from client {}: {}", clientId, json);

            // Try to parse as envelope first
            try {
                MessageEnvelope envelope = objectMapper.readValue(json, MessageEnvelope.class);
                // Valid envelope - check if it has required fields
                if (envelope.getMessageId() != null && envelope.getTopic() != null) {
                    deliveryService.handleIncomingMessage(envelope);
                    return;
                }
            } catch (Exception e) {
                // Not a valid envelope, try legacy format
                log.debug("[PluginMessagingConfig] Message is not an envelope, trying legacy format");
            }

            // Handle legacy format (direct payload without envelope)
            handleLegacyMessage(clientId, json, messageController, clientConnectionService);

        } catch (Exception e) {
            log.error("[PluginMessagingConfig] Error handling message from client {}: {}", clientId, e.getMessage(), e);
        }
    }

    /**
     * Handle legacy message format (without envelope wrapper).
     * This supports older clients that don't use the envelope format.
     */
    @SuppressWarnings("unchecked")
    private void handleLegacyMessage(String clientId, String json,
                                     MessageController messageController, ClientConnectionService clientConnectionService) {
        try {
            Map<String, Object> payload = objectMapper.readValue(json, Map.class);
            log.info("[PluginMessagingConfig] Processing legacy message from client {}: {}", clientId, payload);

            // Check if this is a login request (has email, password, clientId)
            if (payload.containsKey("email") && payload.containsKey("password") && payload.containsKey("clientId")) {
                AppLoginRequest loginRequest = objectMapper.convertValue(payload, AppLoginRequest.class);
                log.info("[PluginMessagingConfig] Routing legacy login request for email: {}", loginRequest.getEmail());
                messageController.handleAppLogin(loginRequest);
                return;
            }

            // Check if this is a pong response
            if ("pong".equals(payload.get("type"))) {
                String pongClientId = clientId != null ? clientId : (String) payload.get("clientId");
                if (pongClientId != null) {
                    log.debug("[PluginMessagingConfig] Routing legacy pong from client: {}", pongClientId);
                    clientConnectionService.handlePong(pongClientId);
                }
                return;
            }

            // Check if this is a task completion
            if (payload.containsKey("taskType") || payload.containsKey("taskId")) {
                String taskType = payload.get("taskType") != null ? payload.get("taskType").toString() : null;
                log.info("[PluginMessagingConfig] Routing legacy task_completed from client: {}", clientId);
                messageController.handleTaskCompleted(payload, taskType);
                return;
            }

            // Check if this is a jobs/assigned request
            if (payload.containsKey("appUserId")) {
                if (clientId != null) {
                    payload.put("clientId", clientId);
                }
                log.info("[PluginMessagingConfig] Routing legacy jobs/assigned request from client: {}", clientId);
                messageController.handleGetAssignedJobs(payload);
                return;
            }

            log.warn("[PluginMessagingConfig] Unknown legacy message format from client {}: {}", clientId, json);

        } catch (Exception e) {
            log.error("[PluginMessagingConfig] Error handling legacy message from client {}: {}", clientId, e.getMessage(), e);
        }
    }
}

