package io.flutter.plugin.localization;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import java.util.Locale;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;

@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4.class)
@TargetApi(24) // LocaleList and scriptCode are API 24+.
public class LocalizationPluginTest {
  private final Context ctx = ApplicationProvider.getApplicationContext();

  // This test should be synced with the version for API 24.
  @Test
  @Config(sdk = Build.VERSION_CODES.O)
  public void computePlatformResolvedLocaleAPI26() {
    // --- Test Setup ---
    FlutterJNI flutterJNI = new FlutterJNI();

    Context context = mock(Context.class);
    Resources resources = mock(Resources.class);
    Configuration config = mock(Configuration.class);
    DartExecutor dartExecutor = mock(DartExecutor.class);
    LocaleList localeList =
        new LocaleList(new Locale("es", "MX"), new Locale("zh", "CN"), new Locale("en", "US"));
    when(context.getResources()).thenReturn(resources);
    when(resources.getConfiguration()).thenReturn(config);
    when(config.getLocales()).thenReturn(localeList);

    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(context, new LocalizationChannel(dartExecutor)));

    // Empty supportedLocales.
    String[] supportedLocales = new String[] {};
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 0);

    // Empty preferredLocales.
    supportedLocales =
        new String[] {
          "fr", "FR", "",
          "zh", "", "",
          "en", "CA", ""
        };
    localeList = new LocaleList();
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The first locale is default.
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "FR");
    assertEquals(result[2], "");

    // Example from https://developer.android.com/guide/topics/resources/multilingual-support#postN
    supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "it", "IT", ""
        };
    localeList = new LocaleList(new Locale("fr", "CH"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The call will use the new (> API 24) algorithm.
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "FR");
    assertEquals(result[2], "");

    supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "fr", "", "",
          "it", "IT", ""
        };
    localeList = new LocaleList(new Locale("fr", "CH"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The call will use the new (> API 24) algorithm.
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "");
    assertEquals(result[2], "");

    // Example from https://developer.android.com/guide/topics/resources/multilingual-support#postN
    supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "it", "IT", ""
        };
    localeList = new LocaleList(new Locale("fr", "CH"), new Locale("it", "CH"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The call will use the new (> API 24) algorithm.
    assertEquals(result.length, 3);
    assertEquals(result[0], "it");
    assertEquals(result[1], "IT");
    assertEquals(result[2], "");

    supportedLocales =
        new String[] {
          "zh", "CN", "Hans",
          "zh", "HK", "Hant",
        };
    localeList = new LocaleList(new Locale("zh", "CN"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 3);
    assertEquals(result[0], "zh");
    assertEquals(result[1], "CN");
    assertEquals(result[2], "Hans");
  }

  // This test should be synced with the version for API 26.
  @Test
  @Config(minSdk = Build.VERSION_CODES.N)
  public void computePlatformResolvedLocale_fromAndroidN() {
    // --- Test Setup ---
    FlutterJNI flutterJNI = new FlutterJNI();

    Context context = mock(Context.class);
    Resources resources = mock(Resources.class);
    Configuration config = mock(Configuration.class);
    DartExecutor dartExecutor = mock(DartExecutor.class);
    LocaleList localeList =
        new LocaleList(new Locale("es", "MX"), new Locale("zh", "CN"), new Locale("en", "US"));
    when(context.getResources()).thenReturn(resources);
    when(resources.getConfiguration()).thenReturn(config);
    when(config.getLocales()).thenReturn(localeList);

    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(context, new LocalizationChannel(dartExecutor)));

    // Empty supportedLocales.
    String[] supportedLocales = new String[] {};
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 0);

    // Empty preferredLocales.
    supportedLocales =
        new String[] {
          "fr", "FR", "",
          "zh", "", "",
          "en", "CA", ""
        };
    localeList = new LocaleList();
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The first locale is default.
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "FR");
    assertEquals(result[2], "");

    // Example from https://developer.android.com/guide/topics/resources/multilingual-support#postN
    supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "it", "IT", ""
        };
    localeList = new LocaleList(new Locale("fr", "CH"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The call will use the new (> API 24) algorithm.
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "FR");
    assertEquals(result[2], "");

    supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "fr", "", "",
          "it", "IT", ""
        };
    localeList = new LocaleList(new Locale("fr", "CH"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The call will use the new (> API 24) algorithm.
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "");
    assertEquals(result[2], "");

    // Example from https://developer.android.com/guide/topics/resources/multilingual-support#postN
    supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "it", "IT", ""
        };
    localeList = new LocaleList(new Locale("fr", "CH"), new Locale("it", "CH"));
    when(config.getLocales()).thenReturn(localeList);
    result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    // The call will use the new (> API 24) algorithm.
    assertEquals(result.length, 3);
    assertEquals(result[0], "it");
    assertEquals(result[1], "IT");
    assertEquals(result[2], "");
  }

  // Tests the legacy pre API 24 algorithm.
  @Test
  @Config(
      minSdk = Build.VERSION_CODES.JELLY_BEAN,
      maxSdk = Build.VERSION_CODES.M,
      qualifiers = "es-rMX")
  public void computePlatformResolvedLocale_emptySupportedLocales_beforeAndroidN() {
    FlutterJNI flutterJNI = new FlutterJNI();
    DartExecutor dartExecutor = mock(DartExecutor.class);
    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor)));
    String[] supportedLocales = new String[] {};
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 0);
  }

  @Test
  @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN, maxSdk = Build.VERSION_CODES.M, qualifiers = "")
  public void computePlatformResolvedLocale_selectFirstLocaleWhenNoUserSetting_beforeAndroidN() {
    FlutterJNI flutterJNI = new FlutterJNI();
    DartExecutor dartExecutor = mock(DartExecutor.class);
    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor)));
    String[] supportedLocales =
        new String[] {
          "fr", "FR", "",
          "zh", "", "",
          "en", "CA", ""
        };
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "FR");
    assertEquals(result[2], "");
  }

  @Test
  @Config(
      minSdk = Build.VERSION_CODES.JELLY_BEAN,
      maxSdk = Build.VERSION_CODES.M,
      qualifiers = "fr-rCH")
  public void computePlatformResolvedLocale_selectFirstLocaleWhenNoExactMatch_beforeAndroidN() {
    FlutterJNI flutterJNI = new FlutterJNI();
    DartExecutor dartExecutor = mock(DartExecutor.class);
    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor)));
    // Example from https://developer.android.com/guide/topics/resources/multilingual-support#postN
    String[] supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "it", "IT", ""
        };
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 3);
    assertEquals(result[0], "en");
    assertEquals(result[1], "");
    assertEquals(result[2], "");
  }

  @Test
  @Config(
      minSdk = Build.VERSION_CODES.JELLY_BEAN,
      maxSdk = Build.VERSION_CODES.M,
      qualifiers = "it-rIT")
  public void computePlatformResolvedLocale_selectExactMatchLocale_beforeAndroidN() {
    FlutterJNI flutterJNI = new FlutterJNI();
    DartExecutor dartExecutor = mock(DartExecutor.class);
    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor)));

    String[] supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "it", "IT", ""
        };
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 3);
    assertEquals(result[0], "it");
    assertEquals(result[1], "IT");
    assertEquals(result[2], "");
  }

  @Test
  @Config(
      minSdk = Build.VERSION_CODES.JELLY_BEAN,
      maxSdk = Build.VERSION_CODES.M,
      qualifiers = "fr-rCH")
  public void computePlatformResolvedLocale_selectOnlyLanguageLocale_beforeAndroidN() {
    FlutterJNI flutterJNI = new FlutterJNI();
    DartExecutor dartExecutor = mock(DartExecutor.class);
    flutterJNI.setLocalizationPlugin(
        new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor)));

    String[] supportedLocales =
        new String[] {
          "en", "", "",
          "de", "DE", "",
          "es", "ES", "",
          "fr", "FR", "",
          "fr", "", "",
          "it", "IT", ""
        };
    String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
    assertEquals(result.length, 3);
    assertEquals(result[0], "fr");
    assertEquals(result[1], "");
    assertEquals(result[2], "");
  }

  // Tests the legacy pre API 21 algorithm.
  @Config(sdk = 16)
  @Test
  public void localeFromString_languageOnly() {
    Locale locale = LocalizationPlugin.localeFromString("en");
    assertEquals(locale, new Locale("en"));
  }

  @Config(sdk = 16)
  @Test
  public void localeFromString_languageAndCountry() {
    Locale locale = LocalizationPlugin.localeFromString("en-US");
    assertEquals(locale, new Locale("en", "US"));
  }

  @Config(sdk = 16)
  @Test
  public void localeFromString_languageCountryAndVariant() {
    Locale locale = LocalizationPlugin.localeFromString("zh-Hans-CN");
    assertEquals(locale, new Locale("zh", "CN", "Hans"));
  }

  @Config(sdk = 16)
  @Test
  public void localeFromString_underscore() {
    Locale locale = LocalizationPlugin.localeFromString("zh_Hans_CN");
    assertEquals(locale, new Locale("zh", "CN", "Hans"));
  }

  @Config(sdk = 16)
  @Test
  public void localeFromString_additionalVariantsAreIgnored() {
    Locale locale = LocalizationPlugin.localeFromString("de-DE-u-co-phonebk");
    assertEquals(locale, new Locale("de", "DE"));
  }

  @Test
  public void getStringResource_withoutLocale() throws JSONException {
    Context context = mock(Context.class);
    Resources resources = mock(Resources.class);
    DartExecutor dartExecutor = mock(DartExecutor.class);
    LocalizationChannel localizationChannel = new LocalizationChannel(dartExecutor);
    LocalizationPlugin plugin = new LocalizationPlugin(context, localizationChannel);

    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);

    String fakePackageName = "package_name";
    String fakeKey = "test_key";
    int fakeId = 123;

    when(context.getPackageName()).thenReturn(fakePackageName);
    when(context.getResources()).thenReturn(resources);
    when(resources.getIdentifier(fakeKey, "string", fakePackageName)).thenReturn(fakeId);
    when(resources.getString(fakeId)).thenReturn("test_value");

    JSONObject param = new JSONObject();
    param.put("key", fakeKey);

    localizationChannel.handler.onMethodCall(
        new MethodCall("Localization.getStringResource", param), mockResult);

    verify(mockResult).success("test_value");
  }

  @Test
  public void getStringResource_withLocale() throws JSONException {
    Context context = mock(Context.class);
    Context localContext = mock(Context.class);
    Resources resources = mock(Resources.class);
    Resources localResources = mock(Resources.class);
    Configuration configuration = new Configuration();
    DartExecutor dartExecutor = mock(DartExecutor.class);
    LocalizationChannel localizationChannel = new LocalizationChannel(dartExecutor);
    LocalizationPlugin plugin = new LocalizationPlugin(context, localizationChannel);

    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);

    String fakePackageName = "package_name";
    String fakeKey = "test_key";
    int fakeId = 123;

    when(context.getPackageName()).thenReturn(fakePackageName);
    when(context.createConfigurationContext(any())).thenReturn(localContext);
    when(context.getResources()).thenReturn(resources);
    when(localContext.getResources()).thenReturn(localResources);
    when(resources.getConfiguration()).thenReturn(configuration);
    when(localResources.getIdentifier(fakeKey, "string", fakePackageName)).thenReturn(fakeId);
    when(localResources.getString(fakeId)).thenReturn("test_value");

    JSONObject param = new JSONObject();
    param.put("key", fakeKey);
    param.put("locale", "en-US");

    localizationChannel.handler.onMethodCall(
        new MethodCall("Localization.getStringResource", param), mockResult);

    verify(mockResult).success("test_value");
  }

  @Test
  public void getStringResource_nonExistentKey() throws JSONException {
    Context context = mock(Context.class);
    Resources resources = mock(Resources.class);
    DartExecutor dartExecutor = mock(DartExecutor.class);
    LocalizationChannel localizationChannel = new LocalizationChannel(dartExecutor);
    LocalizationPlugin plugin = new LocalizationPlugin(context, localizationChannel);

    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);

    String fakePackageName = "package_name";
    String fakeKey = "test_key";

    when(context.getPackageName()).thenReturn(fakePackageName);
    when(context.getResources()).thenReturn(resources);
    when(resources.getIdentifier(fakeKey, "string", fakePackageName))
        .thenReturn(0); // 0 means not exist

    JSONObject param = new JSONObject();
    param.put("key", fakeKey);

    localizationChannel.handler.onMethodCall(
        new MethodCall("Localization.getStringResource", param), mockResult);

    verify(mockResult).success(null);
  }
}
