/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed 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 com.android.textclassifier;

import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS;
import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF;
import static com.google.common.truth.Truth.assertThat;

import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.view.textclassifier.ConversationAction;
import android.view.textclassifier.ConversationActions;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.textclassifier.common.intent.LabeledIntent;
import com.android.textclassifier.common.intent.LabeledIntent.TitleChooser;
import com.android.textclassifier.common.intent.TemplateIntentFactory;
import com.google.android.textclassifier.ActionsSuggestionsModel;
import com.google.android.textclassifier.RemoteActionTemplate;
import com.google.common.collect.ImmutableList;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class ActionsSuggestionsHelperTest {
  private static final String LOCALE_TAG = Locale.US.toLanguageTag();
  private static final Function<CharSequence, List<String>> LANGUAGE_DETECTOR =
      charSequence -> Collections.singletonList(LOCALE_TAG);

  @Test
  public void testToNativeMessages_emptyInput() {
    ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
        ActionsSuggestionsHelper.toNativeMessages(ImmutableList.of(), LANGUAGE_DETECTOR);

    assertThat(conversationMessages).isEmpty();
  }

  @Test
  public void testToNativeMessages_noTextMessages() {
    ConversationActions.Message messageWithoutText =
        new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build();

    ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
        ActionsSuggestionsHelper.toNativeMessages(
            ImmutableList.of(messageWithoutText), LANGUAGE_DETECTOR);

    assertThat(conversationMessages).isEmpty();
  }

  @Test
  public void testToNativeMessages_userIdEncoding() {
    Person.Builder userA = new Person.Builder().setName("userA").setKey("A");
    Person.Builder userB = new Person.Builder().setName("userB").setKey("B");

    ConversationActions.Message firstMessage =
        new ConversationActions.Message.Builder(userB.build()).setText("first").build();
    ConversationActions.Message secondMessage =
        new ConversationActions.Message.Builder(userA.build()).setText("second").build();
    ConversationActions.Message thirdMessage =
        new ConversationActions.Message.Builder(PERSON_USER_SELF).setText("third").build();
    ConversationActions.Message fourthMessage =
        new ConversationActions.Message.Builder(userA.build()).setText("fourth").build();

    ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
        ActionsSuggestionsHelper.toNativeMessages(
            Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage),
            LANGUAGE_DETECTOR);

    assertThat(conversationMessages).hasLength(4);
    assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0);
    assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0);
    assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0);
    assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0);
  }

  @Test
  public void testToNativeMessages_referenceTime() {
    ConversationActions.Message firstMessage =
        new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
            .setText("first")
            .setReferenceTime(createZonedDateTimeFromMsUtc(1000))
            .build();
    ConversationActions.Message secondMessage =
        new ConversationActions.Message.Builder(PERSON_USER_OTHERS).setText("second").build();
    ConversationActions.Message thirdMessage =
        new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
            .setText("third")
            .setReferenceTime(createZonedDateTimeFromMsUtc(2000))
            .build();

    ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
        ActionsSuggestionsHelper.toNativeMessages(
            Arrays.asList(firstMessage, secondMessage, thirdMessage), LANGUAGE_DETECTOR);

    assertThat(conversationMessages).hasLength(3);
    assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000);
    assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0);
    assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000);
  }

  @Test
  public void testDeduplicateActions() {
    Bundle phoneExtras = new Bundle();
    Intent phoneIntent = new Intent();
    phoneIntent.setComponent(new ComponentName("phone", "intent"));
    ExtrasUtils.putActionIntent(phoneExtras, phoneIntent);

    Bundle anotherPhoneExtras = new Bundle();
    Intent anotherPhoneIntent = new Intent();
    anotherPhoneIntent.setComponent(new ComponentName("phone", "another.intent"));
    ExtrasUtils.putActionIntent(anotherPhoneExtras, anotherPhoneIntent);

    Bundle urlExtras = new Bundle();
    Intent urlIntent = new Intent();
    urlIntent.setComponent(new ComponentName("url", "intent"));
    ExtrasUtils.putActionIntent(urlExtras, urlIntent);

    PendingIntent pendingIntent =
        PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), 0, phoneIntent, 0);
    Icon icon = Icon.createWithData(new byte[0], 0, 0);
    ConversationAction action =
        new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE)
            .setAction(new RemoteAction(icon, "label", "1", pendingIntent))
            .setExtras(phoneExtras)
            .build();
    ConversationAction actionWithSameLabel =
        new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE)
            .setAction(new RemoteAction(icon, "label", "2", pendingIntent))
            .setExtras(phoneExtras)
            .build();
    ConversationAction actionWithSamePackageButDifferentClass =
        new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE)
            .setAction(new RemoteAction(icon, "label", "3", pendingIntent))
            .setExtras(anotherPhoneExtras)
            .build();
    ConversationAction actionWithDifferentLabel =
        new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE)
            .setAction(new RemoteAction(icon, "another_label", "4", pendingIntent))
            .setExtras(phoneExtras)
            .build();
    ConversationAction actionWithDifferentPackage =
        new ConversationAction.Builder(ConversationAction.TYPE_OPEN_URL)
            .setAction(new RemoteAction(icon, "label", "5", pendingIntent))
            .setExtras(urlExtras)
            .build();
    ConversationAction actionWithoutRemoteAction =
        new ConversationAction.Builder(ConversationAction.TYPE_CREATE_REMINDER).build();

    List<ConversationAction> conversationActions =
        ActionsSuggestionsHelper.removeActionsWithDuplicates(
            Arrays.asList(
                action,
                actionWithSameLabel,
                actionWithSamePackageButDifferentClass,
                actionWithDifferentLabel,
                actionWithDifferentPackage,
                actionWithoutRemoteAction));

    assertThat(conversationActions).hasSize(3);
    assertThat(conversationActions.get(0).getAction().getContentDescription().toString())
        .isEqualTo("4");
    assertThat(conversationActions.get(1).getAction().getContentDescription().toString())
        .isEqualTo("5");
    assertThat(conversationActions.get(2).getAction()).isNull();
  }

  @Test
  public void testDeduplicateActions_nullComponent() {
    Bundle phoneExtras = new Bundle();
    Intent phoneIntent = new Intent(Intent.ACTION_DIAL);
    ExtrasUtils.putActionIntent(phoneExtras, phoneIntent);
    PendingIntent pendingIntent =
        PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), 0, phoneIntent, 0);
    Icon icon = Icon.createWithData(new byte[0], 0, 0);
    ConversationAction action =
        new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE)
            .setAction(new RemoteAction(icon, "label", "1", pendingIntent))
            .setExtras(phoneExtras)
            .build();
    ConversationAction actionWithSameLabel =
        new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE)
            .setAction(new RemoteAction(icon, "label", "2", pendingIntent))
            .setExtras(phoneExtras)
            .build();

    List<ConversationAction> conversationActions =
        ActionsSuggestionsHelper.removeActionsWithDuplicates(
            Arrays.asList(action, actionWithSameLabel));

    assertThat(conversationActions).isEmpty();
  }

  @Test
  public void createLabeledIntentResult_null() {
    ActionsSuggestionsModel.ActionSuggestion nativeSuggestion =
        new ActionsSuggestionsModel.ActionSuggestion(
            "text", ConversationAction.TYPE_OPEN_URL, 1.0f, null, null, null, null);

    LabeledIntent.Result labeledIntentResult =
        ActionsSuggestionsHelper.createLabeledIntentResult(
            ApplicationProvider.getApplicationContext(),
            new TemplateIntentFactory(),
            nativeSuggestion);

    assertThat(labeledIntentResult).isNull();
  }

  @Test
  public void createLabeledIntentResult_emptyList() {
    ActionsSuggestionsModel.ActionSuggestion nativeSuggestion =
        new ActionsSuggestionsModel.ActionSuggestion(
            "text",
            ConversationAction.TYPE_OPEN_URL,
            1.0f,
            null,
            null,
            new RemoteActionTemplate[0],
            null);

    LabeledIntent.Result labeledIntentResult =
        ActionsSuggestionsHelper.createLabeledIntentResult(
            ApplicationProvider.getApplicationContext(),
            new TemplateIntentFactory(),
            nativeSuggestion);

    assertThat(labeledIntentResult).isNull();
  }

  @Test
  public void createLabeledIntentResult() {
    ActionsSuggestionsModel.ActionSuggestion nativeSuggestion =
        new ActionsSuggestionsModel.ActionSuggestion(
            "text",
            ConversationAction.TYPE_OPEN_URL,
            1.0f,
            null,
            null,
            new RemoteActionTemplate[] {
              new RemoteActionTemplate(
                  "title",
                  null,
                  "description",
                  null,
                  Intent.ACTION_VIEW,
                  Uri.parse("http://www.android.com").toString(),
                  null,
                  0,
                  null,
                  null,
                  null,
                  0)
            },
            null);

    LabeledIntent.Result labeledIntentResult =
        ActionsSuggestionsHelper.createLabeledIntentResult(
            ApplicationProvider.getApplicationContext(),
            new TemplateIntentFactory(),
            nativeSuggestion);

    assertThat(labeledIntentResult.remoteAction.getTitle().toString()).isEqualTo("title");
    assertThat(labeledIntentResult.resolvedIntent.getAction()).isEqualTo(Intent.ACTION_VIEW);
  }

  @Test
  public void createTitleChooser_notOpenUrl() {
    assertThat(ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_CALL_PHONE))
        .isNull();
  }

  @Test
  public void createTitleChooser_openUrl_resolveInfoIsNull() {
    TitleChooser titleChooser =
        ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL);
    LabeledIntent labeledIntent = createWebLabeledIntent();

    assertThat(titleChooser.chooseTitle(labeledIntent, /* resolveInfo= */ null).toString())
        .isEqualTo("titleWithEntity");
  }

  @Test
  public void createTitleChooser_openUrl_packageIsNotAndroidAndHandleAllWebDataUriTrue() {
    TitleChooser titleChooser =
        ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL);
    LabeledIntent labeledIntent = createWebLabeledIntent();

    assertThat(
            titleChooser
                .chooseTitle(
                    labeledIntent,
                    createResolveInfo("com.android.chrome", /* handleAllWebDataURI= */ true))
                .toString())
        .isEqualTo("titleWithEntity");
  }

  @Test
  public void createTitleChooser_openUrl_packageIsNotAndroidAndHandleAllWebDataUriFalse() {
    TitleChooser titleChooser =
        ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL);
    LabeledIntent labeledIntent = createWebLabeledIntent();

    assertThat(
            titleChooser
                .chooseTitle(
                    labeledIntent,
                    createResolveInfo("com.youtube", /* handleAllWebDataURI= */ false))
                .toString())
        .isEqualTo("titleWithoutEntity");
  }

  @Test
  public void createTitleChooser_openUrl_packageIsAndroidAndHandleAllWebDataUriFalse() {
    TitleChooser titleChooser =
        ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL);
    LabeledIntent labeledIntent = createWebLabeledIntent();

    assertThat(
            titleChooser
                .chooseTitle(
                    labeledIntent, createResolveInfo("android", /* handleAllWebDataURI= */ false))
                .toString())
        .isEqualTo("titleWithEntity");
  }

  @Test
  public void createTitleChooser_openUrl_packageIsAndroidAndHandleAllWebDataUriTrue() {
    TitleChooser titleChooser =
        ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL);
    LabeledIntent labeledIntent = createWebLabeledIntent();

    assertThat(
            titleChooser
                .chooseTitle(
                    labeledIntent, createResolveInfo("android", /* handleAllWebDataURI= */ true))
                .toString())
        .isEqualTo("titleWithEntity");
  }

  private LabeledIntent createWebLabeledIntent() {
    Intent webIntent = new Intent(Intent.ACTION_VIEW);
    webIntent.setData(Uri.parse("http://www.android.com"));
    return new LabeledIntent(
        "titleWithoutEntity",
        "titleWithEntity",
        "description",
        "descriptionWithAppName",
        webIntent,
        /* requestCode= */ 0);
  }

  private static ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) {
    return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC"));
  }

  private static void assertNativeMessage(
      ActionsSuggestionsModel.ConversationMessage nativeMessage,
      CharSequence text,
      int userId,
      long referenceTimeInMsUtc) {
    assertThat(nativeMessage.getText()).isEqualTo(text.toString());
    assertThat(nativeMessage.getUserId()).isEqualTo(userId);
    assertThat(nativeMessage.getDetectedTextLanguageTags()).isEqualTo(LOCALE_TAG);
    assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc);
  }

  private static ResolveInfo createResolveInfo(String packageName, boolean handleAllWebDataURI) {
    ResolveInfo resolveInfo = new ResolveInfo();
    resolveInfo.activityInfo = new ActivityInfo();
    resolveInfo.activityInfo.packageName = packageName;
    resolveInfo.handleAllWebDataURI = handleAllWebDataURI;
    return resolveInfo;
  }
}
