// Path: src/main/java/com/hubspot/smtp/utils/SmtpResponses.java
// public final class SmtpResponses {
//   private static final Joiner SPACE_JOINER = Joiner.on(" ").skipNulls();
// 
//   private SmtpResponses() {
//     throw new AssertionError("Cannot create static utility class");
//   }
// 
//   public static String toString(SmtpResponse response) {
//     if (response.details().size() == 0) {
//       return Integer.toString(response.code());
//     } else {
//       return response.code() + " " + SPACE_JOINER.join(response.details());
//     }
//   }
// 
//   public static List<String> getLines(SmtpResponse response) {
//     if (response.details().size() == 0) {
//       return ImmutableList.of(Integer.toString(response.code()));
//     }
// 
//     String[] lines = new String[response.details().size()];
// 
//     for (int i = 0; i < response.details().size(); i++) {
//       StringBuilder responseBuilder = new StringBuilder();
// 
//       responseBuilder.append(response.code());
// 
//       if (i == response.details().size() - 1) {
//         responseBuilder.append(" ");
//       } else {
//         responseBuilder.append("-");
//       }
// 
//       responseBuilder.append(response.details().get(i));
// 
//       lines[i] = responseBuilder.toString();
//     }
// 
//     return ImmutableList.copyOf(lines);
//   }
// 
//   public static boolean isTransientError(SmtpResponse response) {
//     return response.code() >= 400 && response.code() < 500;
//   }
// 
//   public static boolean isPermanentError(SmtpResponse response) {
//     return response.code() >= 500;
//   }
// 
//   public static boolean isError(SmtpResponse response) {
//     return isTransientError(response) || isPermanentError(response);
//   }
// }


import java.time.Duration;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.hubspot.smtp.utils.SmtpResponses;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.smtp.DefaultSmtpRequest;
import io.netty.handler.codec.smtp.SmtpCommand;
import io.netty.handler.codec.smtp.SmtpResponse;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
    if (expectingNoopResponse) {
      LOG.warn("[{}] Did not receive a response to our last NOOP, will not send another", connectionId);
      return;
    }

    Optional<String> debugString = responseHandler.getPendingResponseDebugString();
    if (debugString.isPresent()) {
      LOG.warn("[{}] Waiting for a response to [{}], will not send a NOOP to keep the connection alive", connectionId, debugString.get());
    } else {
      LOG.debug("[{}] Sending NOOP", connectionId);
      ctx.channel().writeAndFlush(new DefaultSmtpRequest(SmtpCommand.NOOP));
      expectingNoopResponse = true;
    }
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (expectingNoopResponse && msg instanceof SmtpResponse) {
      LOG.debug("[{}] NOOP response received {}", connectionId, SmtpResponses.toString((SmtpResponse) msg));
      swallowNoopResponse((SmtpResponse) msg);
      sendPendingWrites(ctx);
      return;
    }

    super.channelRead(ctx, msg);
  }

  private void swallowNoopResponse(SmtpResponse response) throws NoopErrorResponseException {
    expectingNoopResponse = false;

// Path: src/main/java/com/hubspot/smtp/utils/SmtpResponses.java
// public final class SmtpResponses {
//   private static final Joiner SPACE_JOINER = Joiner.on(" ").skipNulls();
// 
//   private SmtpResponses() {
//     throw new AssertionError("Cannot create static utility class");
//   }
// 
//   public static String toString(SmtpResponse response) {
//     if (response.details().size() == 0) {
//       return Integer.toString(response.code());
//     } else {
//       return response.code() + " " + SPACE_JOINER.join(response.details());
//     }
//   }
// 
//   public static List<String> getLines(SmtpResponse response) {
//     if (response.details().size() == 0) {
//       return ImmutableList.of(Integer.toString(response.code()));
//     }
// 
//     String[] lines = new String[response.details().size()];
// 
//     for (int i = 0; i < response.details().size(); i++) {
//       StringBuilder responseBuilder = new StringBuilder();
// 
//       responseBuilder.append(response.code());
// 
//       if (i == response.details().size() - 1) {
//         responseBuilder.append(" ");
//       } else {
//         responseBuilder.append("-");
//       }
// 
//       responseBuilder.append(response.details().get(i));
// 
//       lines[i] = responseBuilder.toString();
//     }
// 
//     return ImmutableList.copyOf(lines);
//   }
// 
//   public static boolean isTransientError(SmtpResponse response) {
//     return response.code() >= 400 && response.code() < 500;
//   }
// 
//   public static boolean isPermanentError(SmtpResponse response) {
//     return response.code() >= 500;
//   }
// 
//   public static boolean isError(SmtpResponse response) {
//     return isTransientError(response) || isPermanentError(response);
//   }
// }


// Path: src/main/java/com/hubspot/smtp/client/KeepAliveHandler.java
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.hubspot.smtp.utils.SmtpResponses;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.smtp.DefaultSmtpRequest;
import io.netty.handler.codec.smtp.SmtpCommand;
import io.netty.handler.codec.smtp.SmtpResponse;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;

    if (expectingNoopResponse) {
      LOG.warn("[{}] Did not receive a response to our last NOOP, will not send another", connectionId);
      return;
    }

    Optional<String> debugString = responseHandler.getPendingResponseDebugString();
    if (debugString.isPresent()) {
      LOG.warn("[{}] Waiting for a response to [{}], will not send a NOOP to keep the connection alive", connectionId, debugString.get());
    } else {
      LOG.debug("[{}] Sending NOOP", connectionId);
      ctx.channel().writeAndFlush(new DefaultSmtpRequest(SmtpCommand.NOOP));
      expectingNoopResponse = true;
    }
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (expectingNoopResponse && msg instanceof SmtpResponse) {
      LOG.debug("[{}] NOOP response received {}", connectionId, SmtpResponses.toString((SmtpResponse) msg));
      swallowNoopResponse((SmtpResponse) msg);
      sendPendingWrites(ctx);
      return;
    }

    super.channelRead(ctx, msg);
  }

  private void swallowNoopResponse(SmtpResponse response) throws NoopErrorResponseException {
    expectingNoopResponse = false;

    if (SmtpResponses.isError(response)) {