using System;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
using Unity.Notifications.Android;

class AndroidNotificationSendingTests
{
    const string kDefaultTestChannel = "default_test_channel";

    class NotificationReceivedHandler
    {
        public int receivedNotificationCount = 0;
        public AndroidNotificationIntentData lastNotification;

        public void OnReceiveNotification(AndroidNotificationIntentData data)
        {
            ++receivedNotificationCount;
            lastNotification = data;
        }
    }

    NotificationReceivedHandler currentHandler;

    [OneTimeSetUp]
    public void BeforeAllTests()
    {
#if !UNITY_EDITOR
        var c = new AndroidNotificationChannel();
        c.Id = kDefaultTestChannel;
        c.Name = "Default Channel 5";
        c.Description = "test_channel 5";
        c.Importance = Importance.High;

        AndroidNotificationCenter.RegisterNotificationChannel(c);
#endif
    }

    [OneTimeTearDown]
    public void AfterAllTests()
    {
        AndroidNotificationCenter.CancelAllNotifications();
    }

    [SetUp]
    public void BeforeEachTest()
    {
#if !UNITY_EDITOR
        AndroidNotificationCenter.CancelAllNotifications();
        currentHandler = new NotificationReceivedHandler();
        AndroidNotificationCenter.OnNotificationReceived += currentHandler.OnReceiveNotification;
#endif
    }

    [TearDown]
    public void AfterEachTest()
    {
#if !UNITY_EDITOR
        AndroidNotificationCenter.OnNotificationReceived -= currentHandler.OnReceiveNotification;
        currentHandler = null;
#endif
    }

    IEnumerator WaitForNotification(float timeout)
    {
        float passed = 0.0f;
        int notificationCount = currentHandler.receivedNotificationCount;
        while (notificationCount == currentHandler.receivedNotificationCount && passed < timeout)
        {
            yield return null;
            passed += Time.deltaTime;
        }
        if (passed > timeout)
            Debug.LogWarning("Timeout waiting for notification");
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotificationExplicitID_NotificationIsReceived()
    {
        int originalId = 456;

        var n = new AndroidNotification();
        n.Title = "SendNotificationExplicitID_NotificationIsReceived : " + originalId.ToString();
        n.Text = "SendNotificationExplicitID_NotificationIsReceived Text";
        n.FireTime = System.DateTime.Now;
        n.Group = "test.dummy.group";


        Debug.LogWarning("SendNotificationExplicitID_NotificationIsReceived sends notification with ID " + originalId);
        AndroidNotificationCenter.SendNotificationWithExplicitID(n, kDefaultTestChannel, originalId);

        yield return WaitForNotification(8.0f);

        Debug.LogWarning("SendNotificationExplicitID_NotificationIsReceived completed. Received notifications: " + currentHandler.receivedNotificationCount);

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        Assert.AreEqual(originalId, currentHandler.lastNotification.Id);
        Assert.AreEqual(n.Group, currentHandler.lastNotification.Notification.Group);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotification_NotificationIsReceived()
    {
        var n = new AndroidNotification();
        n.Title = "SendNotification_NotificationIsReceived";
        n.Text = "SendNotification_NotificationIsReceived Text";
        n.FireTime = System.DateTime.Now;

        Debug.LogWarning("SendNotification_NotificationIsReceived sends notification");
        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);

        yield return WaitForNotification(8.0f);

        Debug.LogWarning("SendNotification_NotificationIsReceived completed. Received notifications: " + currentHandler.receivedNotificationCount);

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        Assert.AreEqual(originalId, currentHandler.lastNotification.Id);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotificationAndCancelNotification_NotificationIsNotReceived()
    {
        var n = new AndroidNotification();
        n.Title = "SendNotificationAndCancelNotification_NotificationIsNotReceived";
        n.Text = "SendNotificationAndCancelNotification_NotificationIsNotReceived Text";
        n.FireTime = System.DateTime.Now.AddSeconds(2.0f);

        Debug.LogWarning("SendNotificationAndCancelNotification_NotificationIsNotReceived sends notification");

        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
        yield return null;
        AndroidNotificationCenter.CancelScheduledNotification(originalId);

        yield return WaitForNotification(8.0f);

        Debug.LogWarning("SendNotificationAndCancelNotification_NotificationIsNotReceived completed.");

        Assert.AreEqual(0, currentHandler.receivedNotificationCount);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator ScheduleRepeatableNotification_NotificationsAreReceived()
    {
        var n = new AndroidNotification();
        n.Title = "Repeating Notification Title";
        n.Text = "Repeating Notification Text";
        n.FireTime = System.DateTime.Now.AddSeconds(2);
        n.RepeatInterval = new System.TimeSpan(0, 0, 5); // interval needs to be quite big for test to be reliable

        Debug.LogWarning("ScheduleRepeatableNotification_NotificationsAreReceived sends notification");

        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);

        // we use inexact scheduling, so repeated notification may take a while to appear
        // inexact also can group, so for test purposes we only check that it repeats at least once
        yield return WaitForNotification(120.0f);
        yield return WaitForNotification(120.0f);
        AndroidNotificationCenter.CancelScheduledNotification(originalId);

        Debug.LogWarning("ScheduleRepeatableNotification_NotificationsAreReceived completed");

        Assert.GreaterOrEqual(currentHandler.receivedNotificationCount, 2);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator NotificationIsScheduled_NotificationStatusIsCorrectlyReported()
    {
        var n = new AndroidNotification();
        n.Title = "NotificationStatusIsCorrectlyReported";
        n.Text = "NotificationStatusIsCorrectlyReported";
        n.FireTime = System.DateTime.Now.AddSeconds(2f);

        Debug.LogWarning("NotificationIsScheduled_NotificationStatusIsCorrectlyReported sends notification");

        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
        yield return null;

        var status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
        Assert.AreEqual(NotificationStatus.Scheduled, status);

        yield return WaitForNotification(120.0f);
        yield return new WaitForSeconds(1.0f);  // give some time for Status Bar to update
        status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
        Assert.AreEqual(NotificationStatus.Delivered, status);

        AndroidNotificationCenter.CancelNotification(originalId);
        yield return new WaitForSeconds(1.5f);

        status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
        Assert.AreEqual(NotificationStatus.Unknown, status);

        Debug.LogWarning("NotificationIsScheduled_NotificationStatusIsCorrectlyReported completed");
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator ArrivedAndUserDismissedNotification_DoesNotReportStatusAsScheduled()
    {
        var n = new AndroidNotification("ArrivedNotificationAndDissmissed", "ArrivedNotificationAndDissmissed", System.DateTime.Now);
        yield return DismissedNotification_DoesNotReportStatusAsScheduled(n);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator ArrivedAndUserDismissedScheduledNotification_DoesNotReportStatusAsScheduled()
    {
        var n = new AndroidNotification("ArrivedNotificationAndDissmissedScheduled", "ArrivedNotificationAndDissmissedScheduled", System.DateTime.Now.AddSeconds(2));
        yield return DismissedNotification_DoesNotReportStatusAsScheduled(n);
    }

    public IEnumerator DismissedNotification_DoesNotReportStatusAsScheduled(AndroidNotification n)
    {
        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
        yield return WaitForNotification(8.0f);

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);

        AndroidNotificationCenter.CancelDisplayedNotification(originalId);
        yield return new WaitForSeconds(2.0f); // cancel is async
        var status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
        Assert.AreEqual(NotificationStatus.Unknown, status);

        // now simulate app kill and restart, where notifications are loaded from persistent storage
        using var managerClass = new AndroidJavaClass("com.unity.androidnotifications.UnityNotificationManager");
        using var manager = managerClass.GetStatic<AndroidJavaObject>("mUnityNotificationManager");
        using var backgroundThread = manager.Get<AndroidJavaObject>("mBackgroundThread");
        using var scheduledNotifications = manager.Get<AndroidJavaObject>("mScheduledNotifications");
        scheduledNotifications.Call("clear");
        backgroundThread.Call("loadNotifications");

        status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
        Assert.AreEqual(NotificationStatus.Unknown, status);
    }

    [Test]
    [UnityPlatform(RuntimePlatform.Android)]
    public void CreateNotificationChannelWithInitializedSettings_ChannelSettingsAreSaved()
    {
        var chOrig = new AndroidNotificationChannel();
        chOrig.Id = "test_channel_settings_are_saved_0";
        chOrig.Name = "spam Channel";
        chOrig.Description = "Generic spam";
        chOrig.Importance = Importance.High;
        chOrig.CanBypassDnd = true;
        chOrig.CanShowBadge = true;
        chOrig.EnableLights = true;
        chOrig.EnableVibration = false;
        // this opne should be read-only, it reports the system setting, but can't be set
        // chOrig.LockScreenVisibility = LockScreenVisibility.Private;

        AndroidNotificationCenter.RegisterNotificationChannel(chOrig);

        var ch = AndroidNotificationCenter.GetNotificationChannel(chOrig.Id);

        Assert.AreEqual(chOrig.Id, ch.Id);
        Assert.AreEqual(chOrig.Name, ch.Name);
        Assert.AreEqual(chOrig.Description, ch.Description);
        Assert.AreEqual(chOrig.Importance, ch.Importance);
        Assert.AreEqual(chOrig.EnableLights, ch.EnableLights);
        Assert.AreEqual(chOrig.EnableVibration, ch.EnableVibration);
        //Assert.AreEqual(chOrig.LockScreenVisibility, ch.LockScreenVisibility);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotification_NotificationIsReceived_CallMainThread()
    {
        var gameObjects = new GameObject[1];

        AndroidNotificationCenter.NotificationReceivedCallback receivedNotificationHandler =
            delegate (AndroidNotificationIntentData data)
        {
            gameObjects[0] = new GameObject();
            gameObjects[0].name = "Hello_World";
            Assert.AreEqual("Hello_World", gameObjects[0].name);
        };

        var n = new AndroidNotification();
        n.Title = "SendNotification_NotificationIsReceived";
        n.Text = "SendNotification_NotificationIsReceived Text";
        n.FireTime = System.DateTime.Now;

        Debug.LogWarning("SendNotification_NotificationIsReceived_CallMainThread sends notification");

        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);

        AndroidNotificationCenter.OnNotificationReceived += receivedNotificationHandler;

        yield return WaitForNotification(8.0f);

        Debug.LogWarning("SendNotification_NotificationIsReceived_CallMainThread completed");

        AndroidNotificationCenter.OnNotificationReceived -= receivedNotificationHandler;
        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        Assert.AreEqual(originalId, currentHandler.lastNotification.Id);
        Assert.IsNotNull(gameObjects[0]);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotification_CanAccessNativeBuilder()
    {
        var n = new AndroidNotification();
        n.Title = "SendNotification_CanAccessNativeBuilder";
        n.Text = "SendNotification_CanAccessNativeBuilder Text";
        n.FireTime = System.DateTime.Now;

        Debug.LogWarning("SendNotification_CanAccessNativeBuilder sends notification");

        using (var builder = AndroidNotificationCenter.CreateNotificationBuilder(n, kDefaultTestChannel))
        {
            using (var extras = builder.Call<AndroidJavaObject>("getExtras"))
            {
                extras.Call("putString", "notification.test.string", "TheTest");
            }

            AndroidNotificationCenter.SendNotification(builder);
        }

        yield return WaitForNotification(8.0f);

        Debug.LogWarning("SendNotification_CanAccessNativeBuilder completed");

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        using (var extras = currentHandler.lastNotification.NativeNotification.Get<AndroidJavaObject>("extras"))
        {
            Assert.AreEqual("TheTest", extras.Call<string>("getString", "notification.test.string"));
        }
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotification_CanReschedule()
    {
        var managerClass = new AndroidJavaClass("com.unity.androidnotifications.UnityNotificationManager");
        var rebootClass = new AndroidJavaClass("com.unity.androidnotifications.UnityNotificationRestartReceiver");
        AndroidJavaObject context;
        using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            using (var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
                context = activity.Call<AndroidJavaObject>("getApplicationContext");
        }

        var n = new AndroidNotification();
        n.Title = "SendNotification_CanReschedule";
        n.Text = "SendNotification_CanReschedule Text";
        n.FireTime = System.DateTime.Now.AddSeconds(5);

        Debug.LogWarning("SendNotification_CanReschedule sends notification");

        int id = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
        yield return new WaitForSeconds(0.2f);

        var manager = managerClass.GetStatic<AndroidJavaObject>("mUnityNotificationManager");
        // clear cached notifications to not mess up future tests
        manager.Get<AndroidJavaObject>("mScheduledNotifications").Call("clear");
        // simulate reboot by directly cancelling scheduled alarms preserving saves
        manager.Call("cancelPendingNotificationIntent", id);
        // temporary null the manager, cause that's what we have in reality
        managerClass.SetStatic<AndroidJavaObject>("mUnityNotificationManager", null);

        yield return new WaitForSeconds(0.2f);
        // simulate reboot by calling reschedule method, that is called after reboot
        rebootClass.CallStatic("rescheduleSavedNotifications", context);

        var newManager = managerClass.GetStatic<AndroidJavaObject>("mUnityNotificationManager");
        // new manager was supposed to be created, assign callback from original one to get notifications
        newManager.Set("mNotificationCallback", manager.Get<AndroidJavaObject>("mNotificationCallback"));

        yield return WaitForNotification(120.0f);

        Debug.LogWarning("SendNotification_CanReschedule completed");

        // restore manager (to not ruin other tests)
        managerClass.SetStatic("mUnityNotificationManager", manager);
        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
    }

    bool RescheduleNotification(AndroidNotification notification)
    {
        AndroidJavaObject manager, currentTime;
        using (var managerClass = new AndroidJavaClass("com.unity.androidnotifications.UnityNotificationManager"))
            manager = managerClass.GetStatic<AndroidJavaObject>("mUnityNotificationManager");
        var rebootClass = new AndroidJavaClass("com.unity.androidnotifications.UnityNotificationRestartReceiver");
        using (var calendarClass = new AndroidJavaClass("java.util.Calendar"))
        {
            using (var calendar = calendarClass.CallStatic<AndroidJavaObject>("getInstance"))
                currentTime = calendar.Call<AndroidJavaObject>("getTime");
        }

        using var builder = AndroidNotificationCenter.CreateNotificationBuilder(789, notification, kDefaultTestChannel);
        var result = rebootClass.CallStatic<bool>("rescheduleNotification", manager, currentTime, builder);
        manager.Dispose();
        currentTime.Dispose();
        return result;
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator RescheduleNotification_ExpiredMomentAgo_SendsNotification()
    {
        var n = new AndroidNotification();
        n.Title = "JustExpired";
        n.Text = "Should be sent";
        n.FireTime = System.DateTime.Now.AddMinutes(-1);

        var result = RescheduleNotification(n);
        Assert.IsTrue(result);

        yield return WaitForNotification(5.0f);

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        var received = currentHandler.lastNotification.Notification;
        Assert.AreEqual("JustExpired", received.Title);
    }

    [Test]
    [UnityPlatform(RuntimePlatform.Android)]
    public void RescheduleNotification_ExpiredNotification_DoesNotReschedule()
    {
        var n = new AndroidNotification();
        n.Title = "LongExpired";
        n.Text = "Should NOT be sent";
        n.FireTime = System.DateTime.Now.AddHours(-1);

        var result = RescheduleNotification(n);
        Assert.IsFalse(result);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendNotificationNotShownInForeground_IsDeliveredButNotShown()
    {
        var n = new AndroidNotification();
        n.Title = "SendNotificationNotShownInForeground_ISDeliveredButNotShown";
        n.Text = "SendNotificationNotShownInForeground_ISDeliveredButNotShown Text";
        n.FireTime = System.DateTime.Now;
        n.ShowInForeground = false;

        Debug.LogWarning("SendNotificationNotShownInForeground_ISDeliveredButNotShown sends notification");

        int originalId = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
        yield return WaitForNotification(5.0f);

        Debug.LogWarning("SendNotificationNotShownInForeground_ISDeliveredButNotShown sends completed");

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        yield return new WaitForSeconds(2.0f);  // give some time, since on some devices we don't immediately get infor on delivered notifications
        var status = AndroidNotificationCenter.CheckScheduledNotificationStatus(originalId);
        Assert.AreEqual(NotificationStatus.Unknown, status);  // status should be unknown, rather than Delivered
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendBigPictureNotification_Roundtrip()
    {
        var n = new AndroidNotification("SendBigPictureNotification_Roundtrip", "SendBigPictureNotification_Roundtrip text", DateTime.Now);
        n.BigPicture = new BigPictureStyle()
        {
            LargeIcon = "icon_1",
            Picture = "icon_0",
            ContentTitle = "ContentTitle",
            ContentDescription = "ContentDescription",
            SummaryText = "summary",
            ShowWhenCollapsed = true,
        };

        Debug.LogWarning("SendBigPictureNotification_Roundtrip send");

        int id = AndroidNotificationCenter.SendNotification(n, kDefaultTestChannel);
        yield return WaitForNotification(8.0f);

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        var received = currentHandler.lastNotification.Notification;
        Assert.AreEqual("SendBigPictureNotification_Roundtrip", received.Title);
        Assert.AreEqual("SendBigPictureNotification_Roundtrip text", received.Text);
        Assert.AreEqual(NotificationStyle.BigPictureStyle, received.Style);
        var bigPicture = received.BigPicture;
        Assert.IsTrue(bigPicture.HasValue);
        var bigPictureData = bigPicture.Value;
        Assert.AreEqual("icon_1", bigPictureData.LargeIcon);
        Assert.AreEqual("icon_0", bigPictureData.Picture);
        Assert.AreEqual("ContentTitle", bigPictureData.ContentTitle);
        Assert.AreEqual("ContentDescription", bigPictureData.ContentDescription);
        Assert.AreEqual("summary", bigPictureData.SummaryText);
        Assert.IsTrue(bigPictureData.ShowWhenCollapsed);
    }

    [UnityTest]
    [UnityPlatform(RuntimePlatform.Android)]
    public IEnumerator SendAndReplaceNotification()
    {
        var original = new AndroidNotification("NotificationToBeReplaced", "This should be replaced", DateTime.Now.AddSeconds(5));
        int id = AndroidNotificationCenter.SendNotification(original, kDefaultTestChannel);
        yield return new WaitForSeconds(1);

        var replacement = new AndroidNotification("ReplacementNotification", "Replacement text", DateTime.Now.AddSeconds(3));
        AndroidNotificationCenter.UpdateScheduledNotification(id, replacement, kDefaultTestChannel);
        yield return WaitForNotification(8.0f);

        Assert.AreEqual(1, currentHandler.receivedNotificationCount);
        var received = currentHandler.lastNotification.Notification;
        Assert.AreEqual(replacement.Title, received.Title);
        Assert.AreEqual(replacement.Text, received.Text);
    }
}
