/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.activemq.bugs;

import java.util.concurrent.TimeoutException;

import jakarta.jms.Connection;
import jakarta.jms.Message;
import jakarta.jms.MessageConsumer;
import jakarta.jms.MessageProducer;
import jakarta.jms.Session;

import org.apache.activemq.EmbeddedBrokerTestSupport;
import org.apache.activemq.broker.region.Queue;
import org.apache.activemq.command.ActiveMQDestination;
import org.junit.Assert;

public class DoubleExpireTest extends EmbeddedBrokerTestSupport {

	private static final long MESSAGE_TTL_MILLIS = 1000;
	private static final long MAX_TEST_TIME_MILLIS = 60000;

	public void setUp() throws Exception {
		setAutoFail(true);
		setMaxTestTime(MAX_TEST_TIME_MILLIS);
		super.setUp();
	}

	/**
	 * This test verifies that a message that expires can be be resent to queue
	 * with a new expiration and that it will be processed as a new message and
	 * allowed to re-expire.
	 * <p>
	 * <b>NOTE:</b> This test fails on AMQ 5.4.2 because the originalExpiration
	 * timestamp is not cleared when the message is resent.
	 */
	public void testDoubleExpireWithoutMove() throws Exception {
		// Create the default dead letter queue.
		final ActiveMQDestination DLQ = createDestination("ActiveMQ.DLQ");

		Connection conn = createConnection();
		try {
			conn.start();
			Session session = conn.createSession(false,
					Session.AUTO_ACKNOWLEDGE);

			// Verify that the test queue and DLQ are empty.
			Assert.assertEquals(0, getSize(destination));
			Assert.assertEquals(0, getSize(DLQ));

			// Enqueue a message to the test queue that will expire after 1s.
			MessageProducer producer = session.createProducer(destination);
			Message testMessage = session.createTextMessage("test message");
			producer.send(testMessage, Message.DEFAULT_DELIVERY_MODE,
					Message.DEFAULT_PRIORITY, MESSAGE_TTL_MILLIS);
			Assert.assertEquals(1, getSize(destination));

			// Wait for the message to expire.
			waitForSize(destination, 0, MAX_TEST_TIME_MILLIS);
			Assert.assertEquals(1, getSize(DLQ));

			// Consume the message from the DLQ and re-enqueue it to the test
			// queue so that it expires after 1s.
			MessageConsumer consumer = session.createConsumer(DLQ);
			Message expiredMessage = consumer.receive();
			Assert.assertEquals(testMessage.getJMSMessageID(), expiredMessage
					.getJMSMessageID());

			producer.send(expiredMessage, Message.DEFAULT_DELIVERY_MODE,
					Message.DEFAULT_PRIORITY, MESSAGE_TTL_MILLIS);
			Assert.assertEquals(1, getSize(destination));
			Assert.assertEquals(0, getSize(DLQ));

			// Verify that the resent message is "different" in that it has
			// another ID.
			Assert.assertNotSame(testMessage.getJMSMessageID(), expiredMessage
					.getJMSMessageID());

			// Wait for the message to re-expire.
			waitForSize(destination, 0, MAX_TEST_TIME_MILLIS);
			Assert.assertEquals(1, getSize(DLQ));

			// Re-consume the message from the DLQ.
			Message reexpiredMessage = consumer.receive();
			Assert.assertEquals(expiredMessage.getJMSMessageID(), reexpiredMessage
					.getJMSMessageID());
		} finally {
			conn.close();
		}
	}

	/**
	 * A helper method that returns the embedded broker's implementation of a
	 * JMS queue.
	 */
	private Queue getPhysicalDestination(ActiveMQDestination destination)
			throws Exception {
		return (Queue) broker.getAdminView().getBroker().getDestinationMap()
				.get(destination);
	}

	/**
	 * A helper method that returns the size of the specified queue/topic.
	 */
	private long getSize(ActiveMQDestination destination) throws Exception {
		return getPhysicalDestination(destination) != null ? getPhysicalDestination(
				destination).getDestinationStatistics().getMessages()
				.getCount()
				: 0;
	}

	/**
	 * A helper method that waits for a destination to reach a certain size.
	 */
	private void waitForSize(ActiveMQDestination destination, int size,
			long timeoutMillis) throws Exception, TimeoutException {
		long startTimeMillis = System.currentTimeMillis();

		while (getSize(destination) != size
				&& System.currentTimeMillis() < (startTimeMillis + timeoutMillis)) {
			Thread.sleep(250);
		}

		if (getSize(destination) != size) {
			throw new TimeoutException("Destination "
					+ destination.getPhysicalName() + " did not reach size "
					+ size + " within " + timeoutMillis + "ms.");
		}
	}
}
