package com.github.messenger4j.test.integration;

import static com.github.messenger4j.send.message.richmedia.RichMediaAsset.Type.IMAGE;
import static com.github.messenger4j.spi.MessengerHttpClient.HttpMethod.POST;
import static java.util.Collections.singletonList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.github.messenger4j.Messenger;
import com.github.messenger4j.common.WebviewHeightRatio;
import com.github.messenger4j.common.WebviewShareButtonState;
import com.github.messenger4j.exception.MessengerApiException;
import com.github.messenger4j.send.MessagePayload;
import com.github.messenger4j.send.MessageResponse;
import com.github.messenger4j.send.MessageTag;
import com.github.messenger4j.send.MessagingType;
import com.github.messenger4j.send.NotificationType;
import com.github.messenger4j.send.SenderActionPayload;
import com.github.messenger4j.send.message.RichMediaMessage;
import com.github.messenger4j.send.message.TemplateMessage;
import com.github.messenger4j.send.message.TextMessage;
import com.github.messenger4j.send.message.quickreply.LocationQuickReply;
import com.github.messenger4j.send.message.quickreply.QuickReply;
import com.github.messenger4j.send.message.quickreply.TextQuickReply;
import com.github.messenger4j.send.message.quickreply.UserEmailQuickReply;
import com.github.messenger4j.send.message.quickreply.UserPhoneNumberQuickReply;
import com.github.messenger4j.send.message.richmedia.ReusableRichMediaAsset;
import com.github.messenger4j.send.message.richmedia.UrlRichMediaAsset;
import com.github.messenger4j.send.message.template.ButtonTemplate;
import com.github.messenger4j.send.message.template.GenericTemplate;
import com.github.messenger4j.send.message.template.ListTemplate;
import com.github.messenger4j.send.message.template.ListTemplate.TopElementStyle;
import com.github.messenger4j.send.message.template.OpenGraphTemplate;
import com.github.messenger4j.send.message.template.ReceiptTemplate;
import com.github.messenger4j.send.message.template.button.Button;
import com.github.messenger4j.send.message.template.button.CallButton;
import com.github.messenger4j.send.message.template.button.LogInButton;
import com.github.messenger4j.send.message.template.button.LogOutButton;
import com.github.messenger4j.send.message.template.button.PostbackButton;
import com.github.messenger4j.send.message.template.button.ShareButton;
import com.github.messenger4j.send.message.template.button.UrlButton;
import com.github.messenger4j.send.message.template.common.DefaultAction;
import com.github.messenger4j.send.message.template.common.Element;
import com.github.messenger4j.send.message.template.opengraph.OpenGraphObject;
import com.github.messenger4j.send.message.template.receipt.Address;
import com.github.messenger4j.send.message.template.receipt.Adjustment;
import com.github.messenger4j.send.message.template.receipt.Item;
import com.github.messenger4j.send.message.template.receipt.Summary;
import com.github.messenger4j.send.recipient.IdRecipient;
import com.github.messenger4j.send.recipient.PhoneNumberRecipient;
import com.github.messenger4j.send.recipient.Recipient;
import com.github.messenger4j.send.recipient.UserRefRecipient;
import com.github.messenger4j.send.senderaction.SenderAction;
import com.github.messenger4j.spi.MessengerHttpClient;
import com.github.messenger4j.spi.MessengerHttpClient.HttpMethod;
import com.github.messenger4j.spi.MessengerHttpClient.HttpResponse;
import java.net.URL;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.skyscreamer.jsonassert.JSONAssert;

/**
 * @author Max Grabenhorst
 * @since 1.0.0
 */
@Slf4j
class SendTest {

  private static final String PAGE_ACCESS_TOKEN = "PAGE_ACCESS_TOKEN";

  private final MessengerHttpClient mockHttpClient = mock(MessengerHttpClient.class);
  private final HttpResponse fakeResponse =
      new HttpResponse(
          200,
          "{\n"
              + "  \"recipient_id\": \"USER_ID\",\n"
              + "  \"message_id\": \"mid.1473372944816:94f72b88c597657974\"\n"
              + "}");

  private final Messenger messenger =
      Messenger.create(PAGE_ACCESS_TOKEN, "test", "test", of(mockHttpClient));

  @BeforeEach
  void beforeEach() throws Exception {
    when(mockHttpClient.execute(any(HttpMethod.class), anyString(), anyString()))
        .thenReturn(fakeResponse);
  }

  @Test
  void shouldSendSenderAction() throws Exception {
    // tag::send-SenderAction[]
    final String recipientId = "USER_ID";
    final SenderAction senderAction = SenderAction.MARK_SEEN;

    final SenderActionPayload payload = SenderActionPayload.create(recipientId, senderAction);

    messenger.send(payload);
    // end::send-SenderAction[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"}," + "\"sender_action\":\"mark_seen\"}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessage() throws Exception {
    // tag::send-TextMessage[]
    final String recipientId = "USER_ID";
    final String text = "Hello Messenger Platform";

    final MessagePayload payload =
        MessagePayload.create(recipientId, MessagingType.RESPONSE, TextMessage.create(text));

    messenger.send(payload);
    // end::send-TextMessage[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"text\":\"Hello Messenger Platform\"}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithPhoneNumberRecipientWithName() throws Exception {
    final Recipient recipient = PhoneNumberRecipient.create("+1 (555) 857-6309", "Jenny", "Doe");
    final String text = "Your package has arrived in Lobby 2.";

    final MessagePayload payload =
        MessagePayload.create(recipient, MessagingType.RESPONSE, TextMessage.create(text));

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"phone_number\":\"+1 (555) 857-6309\", "
            + "\"name\":{\"first_name\":\"Jenny\", \"last_name\":\"Doe\"}}, \n"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"text\":\"Your package has arrived in Lobby 2.\"}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithPhoneNumberRecipientWithoutName() throws Exception {
    final Recipient recipient = PhoneNumberRecipient.create("+1 (555) 857-6309");
    final String text = "Your package has arrived in Lobby 2.";

    final MessagePayload payload =
        MessagePayload.create(recipient, MessagingType.RESPONSE, TextMessage.create(text));

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"phone_number\":\"+1 (555) 857-6309\"}, \n"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"text\":\"Your package has arrived in Lobby 2.\"}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithUserRefRecipient() throws Exception {
    final Recipient recipient = UserRefRecipient.create("<UNIQUE_REF_PARAM>");
    final String text = "hello, world!";

    final MessagePayload payload =
        MessagePayload.create(recipient, MessagingType.RESPONSE, TextMessage.create(text));

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\": {\n"
            + "    \"user_ref\":\"<UNIQUE_REF_PARAM>\"\n"
            + "  }, \n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\": {\n"
            + "    \"text\":\"hello, world!\"\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithNotificationTypeAndMessageTag() throws Exception {
    // tag::send-TextMessageNotificationTypeMessageTag[]
    final Recipient recipient = IdRecipient.create("USER_ID");
    final TextMessage message = TextMessage.create("Hello Messenger Platform");
    final NotificationType notificationType = NotificationType.SILENT_PUSH;
    final MessageTag messageTag = MessageTag.APPLICATION_UPDATE;

    final MessagePayload payload =
        MessagePayload.create(
            recipient, MessagingType.RESPONSE, message, of(notificationType), of(messageTag));

    messenger.send(payload);
    // end::send-TextMessageNotificationTypeMessageTag[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"notification_type\":\"SILENT_PUSH\","
            + "\"tag\":\"APPLICATION_UPDATE\","
            + "\"message\":{\"text\":\"Hello Messenger Platform\"}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithQuickReplies() throws Exception {
    // tag::send-TextMessageQuickReplies[]
    final IdRecipient recipient = IdRecipient.create("<PSID>");

    final String text = "Here is a quick reply!";

    final TextQuickReply quickReplyA =
        TextQuickReply.create(
            "Search", "<POSTBACK_PAYLOAD>", of(new URL("http://example.com/img/red.png")));
    final LocationQuickReply quickReplyB = LocationQuickReply.create();
    final TextQuickReply quickReplyC =
        TextQuickReply.create("Something Else", "<POSTBACK_PAYLOAD>");

    final List<QuickReply> quickReplies = Arrays.asList(quickReplyA, quickReplyB, quickReplyC);

    final TextMessage message = TextMessage.create(text, of(quickReplies), empty());
    final MessagePayload payload =
        MessagePayload.create(recipient, MessagingType.RESPONSE, message);

    messenger.send(payload);
    // end::send-TextMessageQuickReplies[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\":{\n"
            + "    \"id\":\"<PSID>\"\n"
            + "  },\n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\":{\n"
            + "    \"text\": \"Here is a quick reply!\",\n"
            + "    \"quick_replies\":[\n"
            + "      {\n"
            + "        \"content_type\":\"text\",\n"
            + "        \"title\":\"Search\",\n"
            + "        \"payload\":\"<POSTBACK_PAYLOAD>\",\n"
            + "        \"image_url\":\"http://example.com/img/red.png\"\n"
            + "      },\n"
            + "      {\n"
            + "        \"content_type\":\"location\"\n"
            + "      },\n"
            + "      {\n"
            + "        \"content_type\":\"text\",\n"
            + "        \"title\":\"Something Else\",\n"
            + "        \"payload\":\"<POSTBACK_PAYLOAD>\"\n"
            + "      }\n"
            + "    ]\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithEmailAndPhoneNumberQuickReplies() throws Exception {
    final IdRecipient recipient = IdRecipient.create("<PSID>");

    final String text = "Please send us your email or phone number.";

    final UserEmailQuickReply userEmailQuickReply = UserEmailQuickReply.create();
    final UserPhoneNumberQuickReply userPhoneNumberQuickReply = UserPhoneNumberQuickReply.create();

    final List<QuickReply> quickReplies =
        Arrays.asList(userEmailQuickReply, userPhoneNumberQuickReply);

    final TextMessage message = TextMessage.create(text, of(quickReplies), empty());
    final MessagePayload payload =
        MessagePayload.create(recipient, MessagingType.RESPONSE, message);

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\":{\n"
            + "    \"id\":\"<PSID>\"\n"
            + "  },\n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\":{\n"
            + "    \"text\": \"Please send us your email or phone number.\",\n"
            + "    \"quick_replies\":[\n"
            + "      {\n"
            + "        \"content_type\":\"user_email\"\n"
            + "      },\n"
            + "      {\n"
            + "        \"content_type\":\"user_phone_number\"\n"
            + "      }\n"
            + "    ]\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendTextMessageWithMetadata() throws Exception {
    // tag::send-TextMessageMetadata[]
    final IdRecipient recipient = IdRecipient.create("USER_ID");
    final NotificationType notificationType = NotificationType.SILENT_PUSH;
    final String text = "Hello Messenger Platform";
    final String metadata = "DEVELOPER_DEFINED_METADATA";

    final TextMessage textMessage = TextMessage.create(text, empty(), of(metadata));
    final MessagePayload payload =
        MessagePayload.create(
            recipient, MessagingType.RESPONSE, textMessage, of(notificationType), empty());

    messenger.send(payload);
    // end::send-TextMessageMetadata[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"notification_type\":\"SILENT_PUSH\","
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{"
            + "\"text\":\"Hello Messenger Platform\","
            + "\"metadata\":\"DEVELOPER_DEFINED_METADATA\""
            + "}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendImageAttachmentMessageWithUrl() throws Exception {
    // tag::send-ImageMessageUrl[]
    final String recipientId = "USER_ID";
    final String imageUrl = "https://petersapparel.com/img/shirt.png";

    final UrlRichMediaAsset richMediaAsset = UrlRichMediaAsset.create(IMAGE, new URL(imageUrl));
    final RichMediaMessage richMediaMessage = RichMediaMessage.create(richMediaAsset);
    final MessagePayload payload =
        MessagePayload.create(recipientId, MessagingType.RESPONSE, richMediaMessage);

    messenger.send(payload);
    // end::send-ImageMessageUrl[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"attachment\":{"
            + "\"type\":\"image\","
            + "\"payload\":{\"url\":\"https://petersapparel.com/img/shirt.png\"}"
            + "}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendReusableImageAttachmentMessageWithUrl() throws Exception {
    // tag::send-ImageMessageUrlReusable[]
    final IdRecipient recipient = IdRecipient.create("USER_ID");
    final NotificationType notificationType = NotificationType.NO_PUSH;
    final String imageUrl = "https://petersapparel.com/img/shirt.png";

    final UrlRichMediaAsset richMediaAsset =
        UrlRichMediaAsset.create(IMAGE, new URL(imageUrl), of(true));
    final RichMediaMessage richMediaMessage = RichMediaMessage.create(richMediaAsset);
    final MessagePayload payload =
        MessagePayload.create(
            recipient, MessagingType.RESPONSE, richMediaMessage, of(notificationType), empty());

    messenger.send(payload);
    // end::send-ImageMessageUrlReusable[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"notification_type\":\"NO_PUSH\","
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"attachment\":{"
            + "\"type\":\"image\","
            + "\"payload\":{\"url\":\"https://petersapparel.com/img/shirt.png\",\"is_reusable\":true}"
            + "}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendImageAttachmentMessageWithAttachmentId() throws Exception {
    // tag::send-ImageMessageAttachmentId[]
    final IdRecipient recipient = IdRecipient.create("USER_ID");
    final NotificationType notificationType = NotificationType.NO_PUSH;
    final String attachmentId = "1745504518999123";

    final ReusableRichMediaAsset richMediaAsset =
        ReusableRichMediaAsset.create(IMAGE, attachmentId);
    final RichMediaMessage richMediaMessage = RichMediaMessage.create(richMediaAsset);
    final MessagePayload payload =
        MessagePayload.create(
            recipient, MessagingType.RESPONSE, richMediaMessage, of(notificationType), empty());

    messenger.send(payload);
    // end::send-ImageMessageAttachmentId[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"notification_type\":\"NO_PUSH\","
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"attachment\":{"
            + "\"type\":\"image\","
            + "\"payload\":{\"attachment_id\":\"1745504518999123\"}"
            + "}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendButtonTemplateMessage() throws Exception {
    // tag::send-ButtonTemplate[]
    final String recipientId = "USER_ID";

    final UrlButton buttonA =
        UrlButton.create("Show Website", new URL("https://petersapparel.parseapp.com"));
    final PostbackButton buttonB = PostbackButton.create("Start Chatting", "USER_DEFINED_PAYLOAD");
    final UrlButton buttonC =
        UrlButton.create(
            "Show Website",
            new URL("https://petersapparel.parseapp.com"),
            of(WebviewHeightRatio.FULL),
            of(true),
            of(new URL("https://petersfancyapparel.com/fallback")),
            empty());

    final List<Button> buttons = Arrays.asList(buttonA, buttonB, buttonC);
    final ButtonTemplate buttonTemplate =
        ButtonTemplate.create("What do you want to do next?", buttons);

    final TemplateMessage templateMessage = TemplateMessage.create(buttonTemplate);
    final MessagePayload payload =
        MessagePayload.create(recipientId, MessagingType.RESPONSE, templateMessage);

    messenger.send(payload);
    // end::send-ButtonTemplate[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"attachment\":{\"type\":\"template\",\"payload\":{\"text\":\"What do you want to do next?\""
            + ",\"buttons\":[{\"url\":\"https://petersapparel.parseapp.com\",\"title\":\"Show Website\""
            + ",\"type\":\"web_url\"},{\"payload\":\"USER_DEFINED_PAYLOAD\",\"title\":\"Start Chatting\""
            + ",\"type\":\"postback\"},{\"url\":\"https://petersapparel.parseapp.com\",\"webview_height_ratio\":\"full\""
            + ",\"messenger_extensions\":true,\"fallback_url\":\"https://petersfancyapparel.com/fallback\""
            + ",\"title\":\"Show Website\",\"type\":\"web_url\"}],\"template_type\":\"button\"}}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendGenericTemplateMessageWithButtons() throws Exception {
    // tag::send-GenericTemplateButtons[]
    final String recipientId = "USER_ID";

    final List<Button> buttons =
        Arrays.asList(
            UrlButton.create(
                "Select Criteria",
                new URL("https://petersfancyapparel.com/criteria_selector"),
                of(WebviewHeightRatio.FULL),
                of(true),
                of(new URL("https://petersfancyapparel.com/fallback")),
                empty()),
            CallButton.create("Call Representative", "+15105551234"),
            PostbackButton.create("Start Chatting", "DEVELOPER_DEFINED_PAYLOAD"));

    final DefaultAction defaultAction =
        DefaultAction.create(
            new URL("https://peterssendreceiveapp.ngrok.io/view?item=103"),
            of(WebviewHeightRatio.TALL),
            of(true),
            of(new URL("https://peterssendreceiveapp.ngrok.io/")),
            of(WebviewShareButtonState.HIDE));

    final Element element =
        Element.create(
            "Welcome to Peters Hats",
            of("We have got the right hat for everyone."),
            of(new URL("https://petersfancybrownhats.com/company_image.png")),
            of(defaultAction),
            of(buttons));

    final GenericTemplate genericTemplate = GenericTemplate.create(singletonList(element));

    final MessagePayload payload =
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(genericTemplate));

    messenger.send(payload);
    // end::send-GenericTemplateButtons[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\":{\n"
            + "    \"id\":\"USER_ID\"\n"
            + "  },\n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\":{\n"
            + "    \"attachment\":{\n"
            + "      \"type\":\"template\",\n"
            + "      \"payload\":{\n"
            + "        \"template_type\":\"generic\",\n"
            + "        \"elements\":[\n"
            + "           {\n"
            + "            \"title\":\"Welcome to Peters Hats\",\n"
            + "            \"image_url\":\"https://petersfancybrownhats.com/company_image.png\",\n"
            + "            \"subtitle\":\"We have got the right hat for everyone.\",\n"
            + "            \"default_action\": {\n"
            + "              \"type\": \"web_url\",\n"
            + "              \"url\": \"https://peterssendreceiveapp.ngrok.io/view?item=103\",\n"
            + "              \"messenger_extensions\": true,\n"
            + "              \"webview_height_ratio\": \"tall\",\n"
            + "              \"webview_share_button\": \"hide\",\n"
            + "              \"fallback_url\": \"https://peterssendreceiveapp.ngrok.io/\"\n"
            + "            },\n"
            + "            \"buttons\":[\n"
            + "              {\n"
            + "                   \"type\":\"web_url\",\n"
            + "                   \"url\":\"https://petersfancyapparel.com/criteria_selector\",\n"
            + "                   \"title\":\"Select Criteria\",\n"
            + "                   \"webview_height_ratio\": \"full\",\n"
            + "                   \"messenger_extensions\": true,  \n"
            + "                   \"fallback_url\": \"https://petersfancyapparel.com/fallback\"\n"
            + "              },"
            + "               {\n"
            + "                   \"type\":\"phone_number\",\n"
            + "                   \"title\":\"Call Representative\",\n"
            + "                   \"payload\":\"+15105551234\"\n"
            + "               },"
            + "               {\n"
            + "                \"type\":\"postback\",\n"
            + "                \"title\":\"Start Chatting\",\n"
            + "                \"payload\":\"DEVELOPER_DEFINED_PAYLOAD\"\n"
            + "              }              \n"
            + "            ]      \n"
            + "          }\n"
            + "        ]\n"
            + "      }\n"
            + "    }\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendGenericTemplateMessageWithLogInAndLogOutButtons() throws Exception {
    // tag::send-GenericTemplateLoginLogoutButtons[]
    final String recipientId = "USER_ID";

    final List<Button> buttons =
        Arrays.asList(
            LogInButton.create(new URL("https://www.example.com/authorize")),
            LogOutButton.create());

    final Element element =
        Element.create(
            "Welcome to M-Bank",
            empty(),
            of(new URL("http://www.example.com/images/m-bank.png")),
            empty(),
            of(buttons));

    final GenericTemplate genericTemplate = GenericTemplate.create(singletonList(element));

    messenger.send(
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(genericTemplate)));
    // end::send-GenericTemplateLoginLogoutButtons[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":"
            + "{\"attachment\":{\"type\":\"template\",\"payload\":{\"elements\":"
            + "[{\"title\":\"Welcome to M-Bank\",\"image_url\":\"http://www.example.com/images/m-bank.png\","
            + "\"buttons\":[{\"url\":\"https://www.example.com/authorize\",\"type\":\"account_link\"},"
            + "{\"type\":\"account_unlink\"}]}],\"template_type\":\"generic\"}}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendGenericTemplateMessageWithShareButton() throws Exception {
    final String recipientId = "<PSID>";
    final Element element =
        Element.create(
            "Breaking News: Record Thunderstorms",
            of("The local area is due for record thunderstorms over the weekend."),
            of(new URL("https://thechangreport.com/img/lightning.png")),
            empty(),
            of(singletonList(ShareButton.create())));

    final GenericTemplate genericTemplate = GenericTemplate.create(singletonList(element));

    final MessagePayload payload =
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(genericTemplate));

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\":{\n"
            + "    \"id\":\"<PSID>\"\n"
            + "  },\n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\":{\n"
            + "    \"attachment\":{\n"
            + "      \"type\":\"template\",\n"
            + "      \"payload\":{\n"
            + "        \"template_type\":\"generic\",\n"
            + "        \"elements\":[\n"
            + "          {\n"
            + "            \"title\":\"Breaking News: Record Thunderstorms\",\n"
            + "            \"subtitle\":\"The local area is due for record thunderstorms over the weekend.\",\n"
            + "            \"image_url\":\"https://thechangreport.com/img/lightning.png\",\n"
            + "            \"buttons\": [\n"
            + "              {\n"
            + "                \"type\": \"element_share\"\n"
            + "              }\n"
            + "            ]\n"
            + "          }\n"
            + "        ]\n"
            + "      }\n"
            + "    }\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendGenericTemplateMessageWithShareButtonWithShareContents() throws Exception {
    final String recipientId = "<PSID>";

    final GenericTemplate shareGenericTemplate =
        GenericTemplate.create(
            singletonList(
                Element.create(
                    "I took the hat quiz",
                    of("My result: Fez"),
                    of(new URL("https://bot.peters-hats.com/img/hats/fez.jpg")),
                    of(
                        DefaultAction.create(
                            new URL("http://m.me/petershats?ref=invited_by_24601"))),
                    of(
                        singletonList(
                            UrlButton.create(
                                "Take Quiz",
                                new URL("http://m.me/petershats?ref=invited_by_24601")))))));

    final Element element =
        Element.create(
            "Breaking News: Record Thunderstorms",
            of("The local area is due for record thunderstorms over the weekend."),
            of(new URL("https://thechangreport.com/img/lightning.png")),
            empty(),
            of(singletonList(ShareButton.create(of(shareGenericTemplate)))));

    final GenericTemplate genericTemplate = GenericTemplate.create(singletonList(element));

    final MessagePayload payload =
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(genericTemplate));

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\":{\n"
            + "    \"id\":\"<PSID>\"\n"
            + "  },\n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\":{\n"
            + "    \"attachment\":{\n"
            + "      \"type\":\"template\",\n"
            + "      \"payload\":{\n"
            + "        \"template_type\":\"generic\",\n"
            + "        \"elements\":[\n"
            + "          {\n"
            + "            \"title\":\"Breaking News: Record Thunderstorms\",\n"
            + "            \"subtitle\":\"The local area is due for record thunderstorms over the weekend.\",\n"
            + "            \"image_url\":\"https://thechangreport.com/img/lightning.png\",\n"
            + "            \"buttons\": [\n"
            + "              {\n"
            + "                \"type\": \"element_share\",\n"
            + "                \"share_contents\": { \n"
            + "                  \"attachment\": {\n"
            + "                    \"type\": \"template\",\n"
            + "                    \"payload\": {\n"
            + "                      \"template_type\": \"generic\",\n"
            + "                      \"elements\": [\n"
            + "                        {\n"
            + "                          \"title\": \"I took the hat quiz\",\n"
            + "                          \"subtitle\": \"My result: Fez\",\n"
            + "                          \"image_url\": \"https://bot.peters-hats.com/img/hats/fez.jpg\",\n"
            + "                          \"default_action\": {\n"
            + "                            \"type\": \"web_url\",\n"
            + "                            \"url\": \"http://m.me/petershats?ref=invited_by_24601\"\n"
            + "                          },\n"
            + "                          \"buttons\": [\n"
            + "                            {\n"
            + "                              \"type\": \"web_url\",\n"
            + "                              \"url\": \"http://m.me/petershats?ref=invited_by_24601\", \n"
            + "                              \"title\": \"Take Quiz\"\n"
            + "                            }\n"
            + "                          ]\n"
            + "                        }\n"
            + "                      ]\n"
            + "                    }\n"
            + "                  }\n"
            + "                }\n"
            + "              }\n"
            + "            ]\n"
            + "          }\n"
            + "        ]\n"
            + "      }\n"
            + "    }\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendReceiptTemplateMessage() throws Exception {
    // tag::send-ReceiptTemplate[]
    final String recipientId = "USER_ID";

    final Adjustment adjustment1 = Adjustment.create("New Customer Discount", 20.00F);
    final Adjustment adjustment2 = Adjustment.create("$10 Off Coupon", 10.00F);

    final Item item1 =
        Item.create(
            "Classic White T-Shirt",
            50F,
            of("100% Soft and Luxurious Cotton"),
            of(2),
            of("USD"),
            of(new URL("http://petersapparel.parseapp.com/img/whiteshirt.png")));

    final Item item2 =
        Item.create(
            "Classic Gray T-Shirt",
            25F,
            of("100% Soft and Luxurious Cotton"),
            of(1),
            of("USD"),
            of(new URL("http://petersapparel.parseapp.com/img/grayshirt.png")));

    final Address address =
        Address.create("1 Hacker Way", of(""), "Menlo Park", "94025", "CA", "US");
    final Summary summary = Summary.create(56.14F, of(75.00F), of(6.19F), of(4.95F));

    final ReceiptTemplate receiptTemplate =
        ReceiptTemplate.create(
            "Stephane Crozatier",
            "12345678902",
            "Visa 2345",
            "USD",
            summary,
            of(address),
            of(Arrays.asList(item1, item2)),
            of(Arrays.asList(adjustment1, adjustment2)),
            empty(),
            of(new URL("http://petersapparel.parseapp.com/order?order_id=123456")),
            empty(),
            of(ZonedDateTime.of(2015, 4, 7, 22, 14, 12, 0, ZoneOffset.UTC).toInstant()));

    final MessagePayload payload =
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(receiptTemplate));

    messenger.send(payload);
    // end::send-ReceiptTemplate[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"attachment\":{\"type\":\"template\",\"payload\":{\"recipient_name\":\"Stephane Crozatier\","
            + "\"order_number\":\"12345678902\",\"currency\":\"USD\",\"payment_method\":\"Visa 2345\","
            + "\"timestamp\":\"1428444852\",\"order_url\":\"http://petersapparel.parseapp.com/order?order_id\\u003d123456\","
            + "\"elements\":[{\"title\":\"Classic White T-Shirt\",\"subtitle\":\"100% Soft and Luxurious Cotton\","
            + "\"quantity\":2,\"price\":50.00,\"currency\":\"USD\","
            + "\"image_url\":\"http://petersapparel.parseapp.com/img/whiteshirt.png\"},{\"title\":\"Classic Gray T-Shirt\","
            + "\"subtitle\":\"100% Soft and Luxurious Cotton\",\"quantity\":1,\"price\":25.00,\"currency\":\"USD\","
            + "\"image_url\":\"http://petersapparel.parseapp.com/img/grayshirt.png\"}],"
            + "\"address\":{\"street_1\":\"1 Hacker Way\",\"street_2\":\"\",\"city\":\"Menlo Park\",\"postal_code\":\"94025\","
            + "\"state\":\"CA\",\"country\":\"US\"},\"summary\":{\"total_cost\":56.14,\"total_tax\":6.19,"
            + "\"shipping_cost\":4.95,\"subtotal\":75.00},\"adjustments\":[{\"name\":\"New Customer Discount\","
            + "\"amount\":20.00},{\"name\":\"$10 Off Coupon\",\"amount\":10.00}],\"template_type\":\"receipt\"}}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendListTemplateMessage() throws Exception {
    // tag::send-ListTemplate[]
    final String recipientId = "USER_ID";

    final Element element1 =
        Element.create(
            "Classic T-Shirt Collection",
            of("See all our colors"),
            of(new URL("https://peterssendreceiveapp.ngrok.io/img/collection.png")),
            of(
                DefaultAction.create(
                    new URL("https://peterssendreceiveapp.ngrok.io/shop_collection"),
                    of(WebviewHeightRatio.TALL),
                    of(true),
                    of(new URL("https://peterssendreceiveapp.ngrok.io/fallback")),
                    empty())),
            of(
                singletonList(
                    UrlButton.create(
                        "View",
                        new URL("https://peterssendreceiveapp.ngrok.io/collection"),
                        of(WebviewHeightRatio.TALL),
                        empty(),
                        empty(),
                        empty()))));

    final Element element2 =
        Element.create(
            "Classic White T-Shirt",
            of("100% Cotton, 200% Comfortable"),
            of(new URL("https://peterssendreceiveapp.ngrok.io/img/white-t-shirt.png")),
            of(
                DefaultAction.create(
                    new URL("https://peterssendreceiveapp.ngrok.io/view?item=100"),
                    of(WebviewHeightRatio.TALL),
                    empty(),
                    empty(),
                    empty())),
            of(
                singletonList(
                    UrlButton.create(
                        "Shop Now",
                        new URL("https://peterssendreceiveapp.ngrok.io/shop?item=100"),
                        of(WebviewHeightRatio.TALL),
                        empty(),
                        empty(),
                        empty()))));

    final Element element3 =
        Element.create(
            "Classic Blue T-Shirt",
            of("100% Cotton, 200% Comfortable"),
            of(new URL("https://peterssendreceiveapp.ngrok.io/img/blue-t-shirt.png")),
            of(
                DefaultAction.create(
                    new URL("https://peterssendreceiveapp.ngrok.io/view?item=101"),
                    of(WebviewHeightRatio.TALL),
                    empty(),
                    empty(),
                    empty())),
            of(
                singletonList(
                    UrlButton.create(
                        "Shop Now",
                        new URL("https://peterssendreceiveapp.ngrok.io/shop?item=101"),
                        of(WebviewHeightRatio.TALL),
                        empty(),
                        empty(),
                        empty()))));

    final Element element4 =
        Element.create(
            "Classic Black T-Shirt",
            of("100% Cotton, 200% Comfortable"),
            of(new URL("https://peterssendreceiveapp.ngrok.io/img/black-t-shirt.png")),
            of(
                DefaultAction.create(
                    new URL("https://peterssendreceiveapp.ngrok.io/view?item=102"),
                    of(WebviewHeightRatio.TALL),
                    empty(),
                    empty(),
                    empty())),
            of(
                singletonList(
                    UrlButton.create(
                        "Shop Now",
                        new URL("https://peterssendreceiveapp.ngrok.io/shop?item=102"),
                        of(WebviewHeightRatio.TALL),
                        empty(),
                        empty(),
                        empty()))));

    final ListTemplate listTemplate =
        ListTemplate.create(
            Arrays.asList(element1, element2, element3, element4),
            of(TopElementStyle.LARGE),
            of(singletonList(PostbackButton.create("View More", "payload"))));

    messenger.send(
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(listTemplate)));
    // end::send-ListTemplate[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\"recipient\":{\"id\":\"USER_ID\"},"
            + "\"messaging_type\":\"RESPONSE\","
            + "\"message\":{\"attachment\":{\"type\":\"template\","
            + "\"payload\":{\"top_element_style\":\"large\",\"buttons\":[{\"payload\":\"payload\",\"title\":\"View More\","
            + "\"type\":\"postback\"}],\"elements\":[{\"title\":\"Classic T-Shirt Collection\","
            + "\"subtitle\":\"See all our colors\",\"image_url\":\"https://peterssendreceiveapp.ngrok.io/img/collection.png\","
            + "\"buttons\":[{\"url\":\"https://peterssendreceiveapp.ngrok.io/collection\",\"webview_height_ratio\":\"tall\","
            + "\"title\":\"View\",\"type\":\"web_url\"}],\"default_action\":{\"type\":\"web_url\","
            + "\"url\":\"https://peterssendreceiveapp.ngrok.io/shop_collection\",\"webview_height_ratio\":\"tall\","
            + "\"messenger_extensions\":true,\"fallback_url\":\"https://peterssendreceiveapp.ngrok.io/fallback\"}},"
            + "{\"title\":\"Classic White T-Shirt\",\"subtitle\":\"100% Cotton, 200% Comfortable\","
            + "\"image_url\":\"https://peterssendreceiveapp.ngrok.io/img/white-t-shirt.png\","
            + "\"buttons\":[{\"url\":\"https://peterssendreceiveapp.ngrok.io/shop?item\\u003d100\",\"webview_height_ratio\":\"tall\","
            + "\"title\":\"Shop Now\",\"type\":\"web_url\"}],\"default_action\":{\"type\":\"web_url\","
            + "\"url\":\"https://peterssendreceiveapp.ngrok.io/view?item\\u003d100\",\"webview_height_ratio\":\"tall\"}},"
            + "{\"title\":\"Classic Blue T-Shirt\",\"subtitle\":\"100% Cotton, 200% Comfortable\","
            + "\"image_url\":\"https://peterssendreceiveapp.ngrok.io/img/blue-t-shirt.png\","
            + "\"buttons\":[{\"url\":\"https://peterssendreceiveapp.ngrok.io/shop?item\\u003d101\",\"webview_height_ratio\":\"tall\","
            + "\"title\":\"Shop Now\",\"type\":\"web_url\"}],\"default_action\":{\"type\":\"web_url\","
            + "\"url\":\"https://peterssendreceiveapp.ngrok.io/view?item\\u003d101\",\"webview_height_ratio\":\"tall\"}},"
            + "{\"title\":\"Classic Black T-Shirt\",\"subtitle\":\"100% Cotton, 200% Comfortable\","
            + "\"image_url\":\"https://peterssendreceiveapp.ngrok.io/img/black-t-shirt.png\","
            + "\"buttons\":[{\"url\":\"https://peterssendreceiveapp.ngrok.io/shop?item\\u003d102\",\"webview_height_ratio\":\"tall\","
            + "\"title\":\"Shop Now\",\"type\":\"web_url\"}],\"default_action\":{\"type\":\"web_url\","
            + "\"url\":\"https://peterssendreceiveapp.ngrok.io/view?item\\u003d102\",\"webview_height_ratio\":\"tall\"}}],"
            + "\"template_type\":\"list\"}}}}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendOpenGraphTemplateMessage() throws Exception {
    // tag::send-OpenGraphTemplate[]
    final String recipientId = "USER_ID";

    final UrlButton urlButton =
        UrlButton.create("View More", new URL("https://en.wikipedia.org/wiki/Rickrolling"));
    final OpenGraphObject openGraphObject =
        OpenGraphObject.create(
            new URL("https://open.spotify.com/track/7GhIk7Il098yCjg4BQjzvb"),
            of(singletonList(urlButton)));
    final OpenGraphTemplate openGraphTemplate =
        OpenGraphTemplate.create(singletonList(openGraphObject));

    messenger.send(
        MessagePayload.create(
            recipientId, MessagingType.RESPONSE, TemplateMessage.create(openGraphTemplate)));
    // end::send-OpenGraphTemplate[]

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\":{\n"
            + "    \"id\":\"USER_ID\"\n"
            + "  },\n"
            + "  \"messaging_type\":\"RESPONSE\","
            + "  \"message\":{\n"
            + "    \"attachment\":{\n"
            + "      \"type\":\"template\",\n"
            + "      \"payload\":{\n"
            + "        \"template_type\":\"open_graph\",\n"
            + "        \"elements\":[\n"
            + "           {\n"
            + "            \"url\":\"https://open.spotify.com/track/7GhIk7Il098yCjg4BQjzvb\",\n"
            + "            \"buttons\":[\n"
            + "              {\n"
            + "                \"type\":\"web_url\",\n"
            + "                \"url\":\"https://en.wikipedia.org/wiki/Rickrolling\",\n"
            + "                \"title\":\"View More\"\n"
            + "              }              \n"
            + "            ]      \n"
            + "          }\n"
            + "        ]\n"
            + "      }\n"
            + "    }\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldSendButtonTemplateMessageWithUrlButtonWithWebviewShareButtonState() throws Exception {
    final String recipientId = "USER_ID";

    final UrlButton buttonA =
        UrlButton.create("Show Website", new URL("https://petersapparel.parseapp.com"));
    final UrlButton buttonB =
        UrlButton.create(
            "Show Website",
            new URL("https://petersapparel.parseapp.com"),
            of(WebviewHeightRatio.FULL),
            of(true),
            of(new URL("https://petersfancyapparel.com/fallback")),
            of(WebviewShareButtonState.SHOW));
    final UrlButton buttonC =
        UrlButton.create(
            "Show Website",
            new URL("https://petersapparel.parseapp.com"),
            of(WebviewHeightRatio.FULL),
            of(true),
            of(new URL("https://petersfancyapparel.com/fallback")),
            of(WebviewShareButtonState.HIDE));

    final List<Button> buttons = Arrays.asList(buttonA, buttonB, buttonC);
    final ButtonTemplate buttonTemplate =
        ButtonTemplate.create("What do you want to do next?", buttons);

    final TemplateMessage templateMessage = TemplateMessage.create(buttonTemplate);
    final MessagePayload payload =
        MessagePayload.create(recipientId, MessagingType.RESPONSE, templateMessage);

    messenger.send(payload);

    final ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
    final String expectedJsonBody =
        "{\n"
            + "  \"recipient\": {\n"
            + "    \"id\": \"USER_ID\"\n"
            + "  },\n"
            + "  \"messaging_type\": \"RESPONSE\",\n"
            + "  \"message\": {\n"
            + "    \"attachment\": {\n"
            + "      \"type\": \"template\",\n"
            + "      \"payload\": {\n"
            + "        \"text\": \"What do you want to do next?\",\n"
            + "        \"buttons\": [{\n"
            + "          \"url\": \"https://petersapparel.parseapp.com\",\n"
            + "          \"title\": \"Show Website\",\n"
            + "          \"type\": \"web_url\"\n"
            + "        }, {\n"
            + "          \"url\": \"https://petersapparel.parseapp.com\",\n"
            + "          \"webview_height_ratio\": \"full\",\n"
            + "          \"messenger_extensions\": true,\n"
            + "          \"fallback_url\": \"https://petersfancyapparel.com/fallback\",\n"
            + "          \"title\": \"Show Website\",\n"
            + "          \"type\": \"web_url\"\n"
            + "        }, {\n"
            + "          \"url\": \"https://petersapparel.parseapp.com\",\n"
            + "          \"webview_share_button\": \"hide\",\n"
            + "          \"webview_height_ratio\": \"full\",\n"
            + "          \"messenger_extensions\": true,\n"
            + "          \"fallback_url\": \"https://petersfancyapparel.com/fallback\",\n"
            + "          \"title\": \"Show Website\",\n"
            + "          \"type\": \"web_url\"\n"
            + "        }],\n"
            + "        \"template_type\": \"button\"\n"
            + "      }\n"
            + "    }\n"
            + "  }\n"
            + "}";
    verify(mockHttpClient).execute(eq(POST), endsWith(PAGE_ACCESS_TOKEN), payloadCaptor.capture());
    JSONAssert.assertEquals(expectedJsonBody, payloadCaptor.getValue(), true);
  }

  @Test
  void shouldHandleSuccessResponse() throws Exception {
    final HttpResponse successfulResponse =
        new HttpResponse(
            200,
            "{\n"
                + "  \"recipient_id\": \"USER_ID\",\n"
                + "  \"message_id\": \"mid.1473372944816:94f72b88c597657974\",\n"
                + "  \"attachment_id\": \"1745504518999123\"\n"
                + "}");
    when(mockHttpClient.execute(any(HttpMethod.class), anyString(), anyString()))
        .thenReturn(successfulResponse);

    // tag::send-SuccessResponse[]
    final UrlRichMediaAsset richMediaAsset =
        UrlRichMediaAsset.create(IMAGE, new URL("http://image.url"), of(true));
    final MessagePayload payload =
        MessagePayload.create(
            "USER_ID", MessagingType.RESPONSE, RichMediaMessage.create(richMediaAsset));

    final MessageResponse messageResponse = messenger.send(payload);

    final Optional<String> recipientId = messageResponse.recipientId();
    final Optional<String> messageId = messageResponse.messageId();
    final Optional<String> attachmentId = messageResponse.attachmentId();
    log.debug(
        "RecipientId: {} | MessageId: {} | AttachmentId: {}", recipientId, messageId, attachmentId);
    // end::send-SuccessResponse[]

    assertThat(recipientId, is(equalTo(of("USER_ID"))));
    assertThat(messageId, is(equalTo(of("mid.1473372944816:94f72b88c597657974"))));
    assertThat(attachmentId, is(equalTo(of("1745504518999123"))));
  }

  @Test
  void shouldHandleErrorResponse() throws Exception {
    final HttpResponse errorResponse =
        new HttpResponse(
            401,
            "{\n"
                + "  \"error\": {\n"
                + "    \"message\": \"Invalid OAuth access token.\",\n"
                + "    \"type\": \"OAuthException\",\n"
                + "    \"code\": 190,\n"
                + "    \"fbtrace_id\": \"BLBz/WZt8dN\"\n"
                + "  }\n"
                + "}");
    when(mockHttpClient.execute(any(HttpMethod.class), anyString(), anyString()))
        .thenReturn(errorResponse);

    MessengerApiException messengerApiException = null;
    try {
      final MessagePayload payload =
          MessagePayload.create("test", MessagingType.RESPONSE, TextMessage.create("test"));
      messenger.send(payload);
    } catch (MessengerApiException e) {
      messengerApiException = e;
    }

    assertThat(messengerApiException, is(notNullValue()));
    assertThat(messengerApiException.message(), is(equalTo("Invalid OAuth access token.")));
    assertThat(messengerApiException.type(), is(equalTo(of("OAuthException"))));
    assertThat(messengerApiException.code(), is(equalTo(of(190))));
    assertThat(messengerApiException.fbTraceId(), is(equalTo(of("BLBz/WZt8dN"))));
  }
}