import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientConfigurationBuilder;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.StaticSessionCredentialsProvider;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.SimpleConsumer;
import org.apache.rocketmq.client.apis.message.MessageView;
import org.apache.rocketmq.client.apis.producer.Producer;
import org.apache.rocketmq.client.apis.producer.SendReceipt;
import org.junit.Test;
import org.springframework.test.annotation.IfProfileValue;

/**
 * Local RocketMQ real-connection smoke test. Run explicitly with:
 *
 * <pre>
 * mvn -pl rc-service/rc-service-other/other-start -am \
 *   -Drocketmq.local.real=true \
 *   -Dtest=RocketMqLocalSmokeTest#sendAndReceiveOrdinaryMessage test
 *
 * mvn -pl rc-service/rc-service-other/other-start -am \
 *   -Drocketmq.local.real=true \
 *   -Dtest=RocketMqLocalSmokeTest#sendAndReceiveDelayedMessage test
 * </pre>
 */
@IfProfileValue(name = "rocketmq.local.real", value = "true")
public class RocketMqLocalSmokeTest {

  private static final String DEFAULT_ENDPOINT = "127.0.0.1:18081";
  private static final String DEFAULT_TOPIC = "RC_DEFAULT_APP_ORDINARY";
  private static final String DEFAULT_DELAY_TOPIC = "RC_DEFAULT_APP_DELAYED";
  private static final String TAG = "codex_local_mq_smoke";

  @Test
  public void sendAndReceiveOrdinaryMessage() throws Exception {
    String endpoint = System.getProperty("rocketmq.endpoint", DEFAULT_ENDPOINT);
    String topic = System.getProperty("rocketmq.topic", DEFAULT_TOPIC);
    sendAndReceive(endpoint, topic, false);
  }

  @Test
  public void sendAndReceiveDelayedMessage() throws Exception {
    String endpoint = System.getProperty("rocketmq.endpoint", DEFAULT_ENDPOINT);
    String topic = System.getProperty("rocketmq.delayTopic", DEFAULT_DELAY_TOPIC);
    sendAndReceive(endpoint, topic, true);
  }

  private void sendAndReceive(String endpoint, String topic, boolean delayed) throws Exception {
    String accessKey = System.getProperty("rocketmq.accessKey", "local");
    String secretKey = System.getProperty("rocketmq.secretKey", "local");
    String key = "codex-local-mq-" + UUID.randomUUID();
    String body = "rocketmq-local-smoke:" + key;

    ClientServiceProvider provider = ClientServiceProvider.loadService();
    ClientConfiguration clientConfiguration = clientConfiguration(endpoint, accessKey, secretKey);
    Producer producer = provider.newProducerBuilder()
        .setClientConfiguration(clientConfiguration)
        .setTopics(topic)
        .build();
    try {
      org.apache.rocketmq.client.apis.message.MessageBuilder messageBuilder = provider.newMessageBuilder()
          .setTopic(topic)
          .setTag(TAG)
          .setKeys(key)
          .setBody(body.getBytes(StandardCharsets.UTF_8));
      if (delayed) {
        messageBuilder.setDeliveryTimestamp(System.currentTimeMillis() + Duration.ofSeconds(2).toMillis());
      }
      SendReceipt receipt = producer.send(messageBuilder.build());
      assertTrue("message id should not be blank", receipt.getMessageId().toString().length() > 0);
    } finally {
      producer.close();
    }

    String group = "codex_local_mq_smoke_" + UUID.randomUUID().toString().replace("-", "");
    SimpleConsumer consumer = provider.newSimpleConsumerBuilder()
        .setClientConfiguration(clientConfiguration)
        .setConsumerGroup(group)
        .setSubscriptionExpressions(Collections.singletonMap(
            topic, new FilterExpression(TAG, FilterExpressionType.TAG)))
        .setAwaitDuration(Duration.ofSeconds(5))
        .build();
    try {
      MessageView matched = receiveByKey(consumer, key);
      assertEquals(body, decodeBody(matched.getBody()));
      consumer.ack(matched);
    } finally {
      consumer.close();
    }
  }

  private ClientConfiguration clientConfiguration(String endpoint, String accessKey, String secretKey) {
    ClientConfigurationBuilder builder = ClientConfiguration.newBuilder()
        .setEndpoints(endpoint)
        .setRequestTimeout(Duration.ofSeconds(30));
    if (accessKey != null && !accessKey.isEmpty()) {
      builder.setCredentialProvider(new StaticSessionCredentialsProvider(accessKey, secretKey));
    }
    return builder.build();
  }

  private MessageView receiveByKey(SimpleConsumer consumer, String key) throws Exception {
    long deadline = System.currentTimeMillis() + Duration.ofSeconds(30).toMillis();
    while (System.currentTimeMillis() < deadline) {
      List<MessageView> messages = consumer.receive(16, Duration.ofSeconds(15));
      for (MessageView message : messages) {
        if (message.getKeys().contains(key)) {
          return message;
        }
      }
    }
    throw new AssertionError("message not received by key: " + key);
  }

  private String decodeBody(ByteBuffer body) {
    ByteBuffer duplicate = body.asReadOnlyBuffer();
    byte[] bytes = new byte[duplicate.remaining()];
    duplicate.get(bytes);
    return new String(bytes, StandardCharsets.UTF_8);
  }
}
