using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEngine;
#if DAVANCI_NOTIFICATIONS && UNITY_ANDROID
using Unity.Notifications.Android;
#endif
#if DAVANCI_NOTIFICATIONS && (UNITY_IOS || UNITY_TVOS)
using Unity.Notifications.iOS;
#endif

namespace Davanci.Nexus
{
    // Local notifications: D1/D3/D7 retention from NexusConfig + game-scheduled reminders via
    // DavanciNexus.ScheduleNotification. Nothing is shown while the app is in the foreground
    // (ShowInForeground = false; retention pending alerts are cancelled on focus).
    public static class NexusNotifications
    {
        const string AnchorKey = "nexus_retention_anchor_utc";
        const string HashKey = "nexus_retention_schedule_hash";
        const string CustomIdsKey = "nexus_custom_notif_ids";
        const string RetentionChannelId = "nexus_retention";
        const string GameChannelId = "nexus_game";
        const string RetentionIntentPrefix = "nexus_retention_";
        const string CustomIntentPrefix = "nexus_custom_";

        static readonly int[] RetentionDays = { 1, 3, 7 };
        static readonly int[] RetentionAndroidIds = { 9101, 9103, 9107 };

#if DAVANCI_NOTIFICATIONS && UNITY_ANDROID
        static bool _channelsReady;
#endif
        static bool _appInForeground = true;

        public static bool IsAppInForeground => _appInForeground;

        internal static void SetAppInForeground(bool inForeground) => _appInForeground = inForeground;

        // ---- public game API (also exposed on DavanciNexus) --------------------------------

        public static bool ScheduleNotification(int id, string title, string body, DateTime fireTimeLocal)
        {
            if (id <= 0 || string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(body))
                return false;
            if (fireTimeLocal <= DateTime.Now)
                return false;
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            if (!CanSchedule()) return false;
            EnsureListener();
            EnsureChannels();
            string intent = CustomIntentPrefix + id;
            if (!ScheduleAt(id, intent, title, body, fireTimeLocal, GameChannelId))
                return false;
            TrackCustomId(id);
            return true;
#else
            Debug.Log($"[Nexus] ScheduleNotification({id}) skipped — editor or DAVANCI_NOTIFICATIONS off.");
            return false;
#endif
        }

        public static bool ScheduleNotificationIn(int id, string title, string body, double delaySeconds)
        {
            if (delaySeconds <= 0) return false;
            return ScheduleNotification(id, title, body, DateTime.Now.AddSeconds(delaySeconds));
        }

        public static void CancelNotification(int id)
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            UntrackCustomId(id);
#if UNITY_ANDROID
            AndroidNotificationCenter.CancelNotification(id);
#elif UNITY_IOS || UNITY_TVOS
            iOSNotificationCenter.RemoveScheduledNotification(CustomIntentPrefix + id);
#endif
#endif
        }

        public static void CancelAllNotifications()
        {
            CancelRetentionNotifications();
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            foreach (int id in LoadCustomIds().ToArray())
                CancelNotification(id);
#endif
        }

        public static void CancelRetentionNotifications()
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
#if UNITY_ANDROID
            foreach (int id in RetentionAndroidIds)
                AndroidNotificationCenter.CancelNotification(id);
#elif UNITY_IOS || UNITY_TVOS
            foreach (int day in RetentionDays)
                iOSNotificationCenter.RemoveScheduledNotification(RetentionIntentPrefix + day);
#endif
#endif
        }

        public static void RefreshRetentionNotifications()
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            RescheduleRetention();
#endif
        }

        // ---- retention (NexusConfig) ------------------------------------------------------

        internal static void OnPermissionsComplete(bool notificationsAuthorized)
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            if (!notificationsAuthorized) return;
            EnsureListener();
            if (!IsRetentionEnabled()) return;
            EnsureAnchor();
            CancelRetentionNotifications();
#endif
        }

        static void RescheduleRetention()
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            if (!IsRetentionEnabled()) { CancelRetentionNotifications(); return; }
            if (_appInForeground) { CancelRetentionNotifications(); return; }

            var cfg = NexusConfig.Load();
            if (cfg == null) return;

            PlayerPrefs.SetInt(HashKey, ComputeRetentionHash(cfg));
            if (!PlayerPrefs.HasKey(AnchorKey))
                EnsureAnchor();

            long ticks = long.Parse(PlayerPrefs.GetString(AnchorKey, "0"));
            if (ticks <= 0) return;

            var anchor = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
            int hour = Mathf.Clamp(cfg.retentionSendHourLocal, 0, 23);

            CancelRetentionNotifications();
            EnsureChannels();

            for (int i = 0; i < RetentionDays.Length; i++)
            {
                int day = RetentionDays[i];
                if (!IsRetentionDayEnabled(cfg, day)) continue;

                string title = GetRetentionTitle(cfg, day);
                string body = GetRetentionBody(cfg, day);
                if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(body)) continue;

                DateTime fireLocal = anchor.Date.AddDays(day).AddHours(hour);
                if (fireLocal <= DateTime.Now) continue;

                ScheduleAt(RetentionAndroidIds[i], RetentionIntentPrefix + day, title, body, fireLocal,
                    RetentionChannelId);
            }
#endif
        }

        internal static void CheckOpenedFromNotification()
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
#if UNITY_ANDROID
            var intent = AndroidNotificationCenter.GetLastNotificationIntent();
            if (intent == null) return;
            ReportIntent(intent.Notification.IntentData);
#elif UNITY_IOS || UNITY_TVOS
            NexusNotificationsListener.BeginQueryLastResponded();
#endif
#endif
        }

        static void ReportIntent(string intentData)
        {
            if (string.IsNullOrEmpty(intentData)) return;

            if (intentData.StartsWith(RetentionIntentPrefix, StringComparison.Ordinal))
            {
                if (int.TryParse(intentData.Substring(RetentionIntentPrefix.Length), out int day))
                    DavanciNexus.RetentionNotificationOpened(day);
                return;
            }

            if (intentData.StartsWith(CustomIntentPrefix, StringComparison.Ordinal))
            {
                if (int.TryParse(intentData.Substring(CustomIntentPrefix.Length), out int id))
                    DavanciNexus.NotificationOpened(id);
            }
        }

        internal static void ReportOpenedNotification(string identifier) => ReportIntent(identifier);

        // ---- scheduling core ----------------------------------------------------------------

#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
        static bool CanSchedule()
        {
            var cfg = NexusConfig.Load();
            if (cfg == null || !cfg.requestNotifications) return false;
            var auth = AuthorizationManager.Instance;
            return auth == null || auth.AreNotificationsAuthorized;
        }

        static bool IsRetentionEnabled()
        {
            var cfg = NexusConfig.Load();
            return cfg != null && cfg.requestNotifications && cfg.enableRetentionNotifications;
        }

        static bool ScheduleAt(int androidId, string iosIdentifier, string title, string body,
            DateTime fireLocal, string channelId)
        {
#if UNITY_ANDROID
            var notification = new AndroidNotification
            {
                Title = title,
                Text = body,
                FireTime = fireLocal,
                IntentData = iosIdentifier,
                ShowInForeground = false,
            };
            AndroidNotificationCenter.SendNotificationWithExplicitID(notification, channelId, androidId);
            return true;
#elif UNITY_IOS || UNITY_TVOS
            var iosNotification = new iOSNotification
            {
                Identifier = iosIdentifier,
                Title = title,
                Body = body,
                ShowInForeground = false,
                Trigger = new iOSNotificationCalendarTrigger
                {
                    Year = fireLocal.Year,
                    Month = fireLocal.Month,
                    Day = fireLocal.Day,
                    Hour = fireLocal.Hour,
                    Minute = fireLocal.Minute,
                    Second = fireLocal.Second,
                    Repeats = false,
                },
            };
            iOSNotificationCenter.ScheduleNotification(iosNotification);
            return true;
#else
            return false;
#endif
        }

        static void EnsureChannels()
        {
#if UNITY_ANDROID
            if (_channelsReady) return;
            AndroidNotificationCenter.RegisterNotificationChannel(new AndroidNotificationChannel
            {
                Id = RetentionChannelId,
                Name = "Retention",
                Importance = Importance.Default,
                Description = "Come-back reminders",
            });
            AndroidNotificationCenter.RegisterNotificationChannel(new AndroidNotificationChannel
            {
                Id = GameChannelId,
                Name = "Game",
                Importance = Importance.Default,
                Description = "In-game scheduled reminders",
            });
            _channelsReady = true;
#endif
        }

        static void EnsureListener()
        {
            if (NexusNotificationsListener.Exists) return;
            var go = new GameObject("[Nexus] Notifications");
            go.AddComponent<NexusNotificationsListener>();
            UnityEngine.Object.DontDestroyOnLoad(go);
        }
#endif

        static void EnsureAnchor()
        {
            if (PlayerPrefs.HasKey(AnchorKey)) return;
            PlayerPrefs.SetString(AnchorKey, DateTime.UtcNow.Ticks.ToString());
            PlayerPrefs.Save();
        }

        static int ComputeRetentionHash(NexusConfig cfg)
        {
            unchecked
            {
                int h = 17;
                h = h * 31 + cfg.retentionSendHourLocal;
                h = h * 31 + (cfg.retentionD1Enabled ? 1 : 0);
                h = h * 31 + (cfg.retentionD3Enabled ? 1 : 0);
                h = h * 31 + (cfg.retentionD7Enabled ? 1 : 0);
                h = h * 31 + (cfg.retentionD1Title ?? "").GetHashCode();
                h = h * 31 + (cfg.retentionD1Body ?? "").GetHashCode();
                h = h * 31 + (cfg.retentionD3Title ?? "").GetHashCode();
                h = h * 31 + (cfg.retentionD3Body ?? "").GetHashCode();
                h = h * 31 + (cfg.retentionD7Title ?? "").GetHashCode();
                h = h * 31 + (cfg.retentionD7Body ?? "").GetHashCode();
                return h;
            }
        }

        static bool IsRetentionDayEnabled(NexusConfig cfg, int day)
        {
            switch (day)
            {
                case 1: return cfg.retentionD1Enabled;
                case 3: return cfg.retentionD3Enabled;
                case 7: return cfg.retentionD7Enabled;
                default: return false;
            }
        }

        static string GetRetentionTitle(NexusConfig cfg, int day)
        {
            switch (day)
            {
                case 1: return cfg.retentionD1Title;
                case 3: return cfg.retentionD3Title;
                case 7: return cfg.retentionD7Title;
                default: return "";
            }
        }

        static string GetRetentionBody(NexusConfig cfg, int day)
        {
            switch (day)
            {
                case 1: return cfg.retentionD1Body;
                case 3: return cfg.retentionD3Body;
                case 7: return cfg.retentionD7Body;
                default: return "";
            }
        }

        static void TrackCustomId(int id)
        {
            var ids = LoadCustomIds();
            if (!ids.Add(id)) return;
            SaveCustomIds(ids);
        }

        static void UntrackCustomId(int id)
        {
            var ids = LoadCustomIds();
            if (!ids.Remove(id)) return;
            SaveCustomIds(ids);
        }

        static HashSet<int> LoadCustomIds()
        {
            var set = new HashSet<int>();
            string raw = PlayerPrefs.GetString(CustomIdsKey, "");
            if (string.IsNullOrEmpty(raw)) return set;
            foreach (string part in raw.Split(','))
                if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out int id))
                    set.Add(id);
            return set;
        }

        static void SaveCustomIds(HashSet<int> ids)
        {
            PlayerPrefs.SetString(CustomIdsKey, string.Join(",", ids.OrderBy(i => i)));
            PlayerPrefs.Save();
        }
    }

    // Cancels pending retention while the app is open; reschedules when the player leaves.
    public sealed class NexusNotificationsListener : MonoBehaviour
    {
        public static bool Exists { get; private set; }
        static NexusNotificationsListener _instance;

        void Awake()
        {
            Exists = true;
            _instance = this;
            NexusNotifications.SetAppInForeground(Application.isFocused);
        }

        void OnDestroy()
        {
            if (Exists) Exists = false;
            if (_instance == this) _instance = null;
        }

#if DAVANCI_NOTIFICATIONS && (UNITY_IOS || UNITY_TVOS) && !UNITY_EDITOR
        internal static void BeginQueryLastResponded()
        {
            if (_instance == null) return;
            _instance.StartCoroutine(_instance.QueryLastResponded());
        }

        IEnumerator QueryLastResponded()
        {
            var op = iOSNotificationCenter.QueryLastRespondedNotification();
            yield return op;
            if (op.State != QueryLastRespondedNotificationState.HaveRespondedNotification)
                yield break;
            var notification = op.Notification;
            if (notification == null) yield break;
            NexusNotifications.ReportOpenedNotification(notification.Identifier);
        }
#endif

        void OnApplicationFocus(bool hasFocus)
        {
#if DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            NexusNotifications.SetAppInForeground(hasFocus);
            if (hasFocus)
            {
                NexusNotifications.CheckOpenedFromNotification();
                NexusNotifications.CancelRetentionNotifications();
            }
            else
            {
                NexusNotifications.RefreshRetentionNotifications();
            }
#endif
        }

        void OnApplicationPause(bool paused)
        {
#if UNITY_ANDROID && DAVANCI_NOTIFICATIONS && !UNITY_EDITOR
            NexusNotifications.SetAppInForeground(!paused);
            if (paused)
                NexusNotifications.RefreshRetentionNotifications();
            else
            {
                NexusNotifications.CheckOpenedFromNotification();
                NexusNotifications.CancelRetentionNotifications();
            }
#endif
        }
    }
}
