{%- import "util.jinja.include" as util -%}
{%- import "cloudevents.jinja.include" as cloudEvents -%}
{%- import "amqp.jinja.include" as amqp -%}
{{ util.CommonFileHeader() }}

package {{ project_name | lower | replace('-', '_') }};

import org.apache.qpid.protonj2.client.Client;
import org.apache.qpid.protonj2.client.Connection;
import org.apache.qpid.protonj2.client.ConnectionOptions;
import org.apache.qpid.protonj2.client.Delivery;
import org.apache.qpid.protonj2.client.Message;
import org.apache.qpid.protonj2.client.Receiver;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;

/**
 * AMQP 1.0 consumer using Apache Qpid ProtonJ2
 */
class AmqpConsumer {
    private static final Logger logger = LogManager.getLogger(AmqpConsumer.class);
    private final Client client;
    private final EndpointCredential credential;
    private final List<URI> endpoints;
    private final String node;
    private final TriConsumer<Message<?>, MessageContext, Logger> messageHandler;
    private final AtomicBoolean running = new AtomicBoolean(false);
    private final Map<URI, ConnectionInfo> connectionCache = new HashMap<>();
    
    private static class ConnectionInfo {
        final Connection connection;
        final Receiver receiver;
        
        ConnectionInfo(Connection connection, Receiver receiver) {
            this.connection = connection;
            this.receiver = receiver;
        }
    }
    
    public AmqpConsumer(
        EndpointCredential credential,
        Map<String, String> options,
        List<URI> endpoints,
        TriConsumer<Message<?>, MessageContext, Logger> messageHandler
    ) {
        this.client = Client.create();
        this.credential = credential;
        this.endpoints = endpoints;
        this.node = options.getOrDefault("node", null);
        this.messageHandler = messageHandler;
    }
    
    /**
     * Start the consumer - begins receiving messages
     */
    public void start() throws Exception {
        if (running.compareAndSet(false, true)) {
            // Create connections and start receiving for all endpoints
            for (URI endpoint : endpoints) {
                ConnectionInfo connInfo = getOrCreateConnection(endpoint);
                
                // Start background thread to receive messages from this endpoint
                Thread receiverThread = new Thread(() -> {
                    try {
                        while (running.get()) {
                            try {
                                Delivery delivery = connInfo.receiver.receive(1000, TimeUnit.MILLISECONDS);
                                if (delivery != null) {
                                    Message<?> message = delivery.message();
                                    MessageContext context = new MessageContext(delivery, logger);
                                    try {
                                        messageHandler.accept(message, context, logger);
                                        // If handler didn't handle disposition, auto-reject on error
                                        if (!context.isDispositionHandled()) {
                                            logger.warn("Handler did not accept, release, reject, or modify the message. Auto-rejecting.");
                                            context.reject("Handler error", "Message disposition not handled by handler");
                                        }
                                    } catch (Exception ex) {
                                        logger.error("Error processing message: " + ex.getMessage(), ex);
                                        if (!context.isDispositionHandled()) {
                                            context.reject("Processing error", "Failed to process message: " + ex.getMessage());
                                        }
                                    }
                                }
                            } catch (Exception ex) {
                                if (running.get()) {
                                    logger.error("Error receiving message from " + endpoint + ": " + ex.getMessage(), ex);
                                }
                            }
                        }
                    } finally {
                        logger.info("Receiver thread for " + endpoint + " stopped");
                    }
                });
                receiverThread.setDaemon(true);
                receiverThread.setName("amqp-receiver-" + endpoint.getHost());
                receiverThread.start();
            }
        }
    }
    
    /**
     * Stop the consumer
     */
    public void stop() {
        if (running.compareAndSet(true, false)) {
            // Close all connections
            for (ConnectionInfo connInfo : connectionCache.values()) {
                try {
                    if (connInfo.receiver != null) {
                        connInfo.receiver.close();
                    }
                    if (connInfo.connection != null) {
                        connInfo.connection.close();
                    }
                } catch (Exception ex) {
                    logger.error("Error closing connection: " + ex.getMessage(), ex);
                }
            }
            connectionCache.clear();
            
            try {
                if (client != null) {
                    client.close();
                }
            } catch (Exception ex) {
                logger.error("Error closing client: " + ex.getMessage(), ex);
            }
        }
    }
    
    /**
     * Gets or creates a connection to the specified endpoint
     */
    private ConnectionInfo getOrCreateConnection(URI endpoint) throws Exception {
        return connectionCache.computeIfAbsent(endpoint, ep -> {
            try {
                String host = ep.getHost();
                int port = ep.getPort() == -1 ?
                    (ep.getScheme().equalsIgnoreCase("amqps") ? 5671 : 5672) :
                    ep.getPort();
                String path = node != null ? node : ep.getPath();
                // Remove leading slash from path if present
                if (path != null && path.startsWith("/")) {
                    path = path.substring(1);
                }
                
                // Build connection options
                ConnectionOptions options = new ConnectionOptions();
                
                if (credential instanceof PlainEndpointCredential) {
                    PlainEndpointCredential plain = (PlainEndpointCredential) credential;
                    options.user(plain.getUsername());
                    options.password(plain.getPassword());
                }
                
                // Create connection
                Connection connection = client.connect(host, port, options);
                
                // Create receiver
                String address = (path != null && !path.isEmpty()) ? path : "queue";
                Receiver receiver = connection.openReceiver(address);
                
                logger.info("Connected to " + host + ":" + port + " and receiving from " + address);
                
                return new ConnectionInfo(connection, receiver);
            } catch (Exception ex) {
                logger.error("Failed to create connection to " + ep + ": " + ex.getMessage(), ex);
                throw new RuntimeException(ex);
            }
        });
    }
}
