using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEngine;
using UnityEngine.Networking;

namespace Davanci.Nexus.Editor
{
    // Downloads and imports individual SDK updates published alongside DavanciNexus releases.
    [InitializeOnLoad]
    public static class NexusPackageUpdater
    {
        // The package being imported ({id, version, temp}). An SDK package with scripts makes Unity
        // reload the domain before importPackageCompleted reaches us, which wipes the callback;
        // this key survives the reload so the import is still recorded when the editor comes back.
        const string KeyImporting = "Davanci.Nexus.PackageImporting";

        [Serializable] class PendingImport { public string id; public string version; public string temp; }

        static bool _downloading, _upmBusy;

        public static bool UpdateInProgress => _downloading || _upmBusy || UpdatingId != null;

        // Id of the SDK whose package is importing right now, or null.
        public static string UpdatingId => LoadPending()?.id;

        // Raised when an import starts or finishes, so open windows can redraw.
        public static event Action Changed;

        static NexusPackageUpdater() => EditorApplication.delayCall += Resume;

        public static void UpdatePackage(string packageId, Action<bool> onComplete = null)
        {
            if (UpdateInProgress)
            {
                onComplete?.Invoke(false);
                return;
            }

            var status = NexusPackageCatalog.GetStatus(packageId);
            if (status.Entry == null)
            {
                onComplete?.Invoke(false);
                return;
            }

            if (status.Entry.kind == "upm")
            {
                UpdateUpm(status, onComplete);
                return;
            }

            string localPath = status.LocalPackagePath;
            bool remoteNewer = !string.IsNullOrEmpty(status.RemoteUrl)
                && !string.IsNullOrEmpty(status.RemoteVersion)
                && NexusUpdater.CompareVersions(status.RemoteVersion, status.BundledVersion) > 0;

            if (remoteNewer && string.IsNullOrEmpty(localPath))
            {
                DownloadAndImport(packageId, status.RemoteUrl, status.RemoteVersion, status.Entry, onComplete);
                return;
            }

            if (!string.IsNullOrEmpty(localPath))
            {
                ImportLocal(packageId, localPath, status.TargetVersion ?? status.BundledVersion, status.Entry, onComplete);
                return;
            }

            if (!string.IsNullOrEmpty(status.RemoteUrl))
            {
                DownloadAndImport(packageId, status.RemoteUrl, status.TargetVersion, status.Entry, onComplete);
                return;
            }

            EditorUtility.DisplayDialog("DavanciNexus",
                "No update package found locally or remotely for this SDK.", "OK");
            onComplete?.Invoke(false);
        }

        static void UpdateUpm(NexusPackageCatalog.PackageStatus status, Action<bool> onComplete)
        {
            string target = status.TargetVersion ?? status.BundledVersion;
            if (string.IsNullOrEmpty(target) || string.IsNullOrEmpty(status.Entry.upmId))
            {
                onComplete?.Invoke(false);
                return;
            }

            _upmBusy = true;
            Changed?.Invoke();
            var req = Client.Add($"{status.Entry.upmId}@{target}");
            EditorApplication.update += Poll;

            void Poll()
            {
                if (!req.IsCompleted) return;
                EditorApplication.update -= Poll;
                _upmBusy = false;
                Changed?.Invoke();

                if (req.Status == StatusCode.Success)
                {
                    NexusPackageCatalog.StampInstalled(status.Entry.id, target);
                    SetDefineForUpm(status.Entry.id);
                    Debug.Log($"[Nexus] Updated {status.Entry.displayName} to {target}.");
                    onComplete?.Invoke(true);
                }
                else
                {
                    Debug.LogWarning($"[Nexus] UPM update failed: {req.Error?.message}");
                    onComplete?.Invoke(false);
                }
            }
        }

        static void SetDefineForUpm(string id)
        {
            switch (id)
            {
                case "unity_iap": NexusIapInstaller.SetDefine(true); break;
                case "mobile_notifications": NexusNotificationsInstaller.SetDefine(true); break;
            }
        }

        static void ImportLocal(string packageId, string path, string version,
            NexusPackageCatalog.PackageEntry entry, Action<bool> onComplete, string tempToDelete = null)
        {
            SetDefineForPackage(entry);
            ImportTracked(packageId, path, version, onComplete, tempToDelete);
        }

        // Imports a .unitypackage and records its version once the files are in the project. Safe
        // across the domain reload the import causes: onComplete only runs when no reload happens
        // in between, so windows should also listen to Changed.
        public static void ImportTracked(string packageId, string path, string version,
            Action<bool> onComplete = null, string tempToDelete = null)
        {
            void Done(bool ok)
            {
                AssetDatabase.importPackageCompleted -= Imported;
                AssetDatabase.importPackageCancelled -= Cancelled;
                AssetDatabase.importPackageFailed -= Failed;
                if (UpdatingId != packageId) return; // already finished after a reload
                Finish(ok);
                onComplete?.Invoke(ok);
            }
            void Imported(string _) => Done(true);
            void Cancelled(string _) => Done(false);
            void Failed(string _, string message)
            {
                Debug.LogError($"[Nexus] Importing {packageId} failed: {message}");
                Done(false);
            }

            AssetDatabase.importPackageCompleted += Imported;
            AssetDatabase.importPackageCancelled += Cancelled;
            AssetDatabase.importPackageFailed += Failed;
            SessionState.SetString(KeyImporting, JsonUtility.ToJson(
                new PendingImport { id = packageId, version = version, temp = tempToDelete }));
            Changed?.Invoke();
            AssetDatabase.ImportPackage(path, false);
        }

        // Back from the reload an import caused: the files are in, so record the version.
        static void Resume()
        {
            if (EditorApplication.isCompiling || EditorApplication.isUpdating)
            {
                EditorApplication.delayCall += Resume;
                return;
            }
            if (LoadPending() != null) Finish(true);
            RemoveSupersededFiles();
        }

        // Files an SDK update renamed. The SDK expects the import to move them by GUID, which does
        // not always happen; the leftover copy then fails every iOS build (duplicate GA_Wrapper
        // members, duplicate native symbols). Only deleted when the replacement is there too.
        static readonly (string obsolete, string replacement)[] Superseded =
        {
            // GameAnalytics 8: the iOS/tvOS wrappers and bridges merged into Plugins/Apple.
            ("Assets/GameAnalytics/Plugins/Scripts/Wrapper/GA_iOSWrapper.cs",
                "Assets/GameAnalytics/Plugins/Scripts/Wrapper/GA_AppleWrapper.cs"),
            ("Assets/GameAnalytics/Plugins/iOS/GameAnalyticsUnity.m",
                "Assets/GameAnalytics/Plugins/Apple/GameAnalyticsUnity.m"),
        };

        static void RemoveSupersededFiles()
        {
            foreach (var (obsolete, replacement) in Superseded)
            {
                if (File.Exists(obsolete) && File.Exists(replacement) && AssetDatabase.DeleteAsset(obsolete))
                    Debug.Log($"[Nexus] Removed {obsolete}; the updated SDK replaced it with {replacement}.");
            }
        }

        static void Finish(bool imported)
        {
            var pending = LoadPending();
            SessionState.EraseString(KeyImporting);
            if (pending == null) return;

            if (!string.IsNullOrEmpty(pending.temp))
                try { if (File.Exists(pending.temp)) File.Delete(pending.temp); } catch { /* temp file */ }

            if (imported && !string.IsNullOrEmpty(pending.version))
            {
                NexusPackageCatalog.StampInstalled(pending.id, pending.version);
                Debug.Log($"[Nexus] Imported {pending.id} {pending.version}.");
            }
            if (imported) RemoveSupersededFiles();
            Changed?.Invoke();
        }

        static PendingImport LoadPending()
        {
            string json = SessionState.GetString(KeyImporting, "");
            if (string.IsNullOrEmpty(json)) return null;
            try
            {
                var pending = JsonUtility.FromJson<PendingImport>(json);
                return string.IsNullOrEmpty(pending?.id) ? null : pending;
            }
            catch { return null; }
        }

        static void DownloadAndImport(string packageId, string url, string version,
            NexusPackageCatalog.PackageEntry entry, Action<bool> onComplete)
        {
            if (string.IsNullOrEmpty(url))
            {
                onComplete?.Invoke(false);
                return;
            }

            _downloading = true;
            Changed?.Invoke();
            string temp = Path.Combine(Path.GetTempPath(), $"NexusPkg_{packageId}_{version}.unitypackage");
            var req = UnityWebRequest.Get(url);
            req.SetRequestHeader("Cache-Control", "no-cache");
            req.downloadHandler = new DownloadHandlerFile(temp) { removeFileOnAbort = true };
            var op = req.SendWebRequest();

            void Poll()
            {
                if (EditorUtility.DisplayCancelableProgressBar("DavanciNexus",
                        $"Downloading {entry.displayName} v{version}…", req.downloadProgress))
                    req.Abort();

                if (!op.isDone) return;
                EditorApplication.update -= Poll;
                EditorUtility.ClearProgressBar();
                _downloading = false;

                bool ok = req.result == UnityWebRequest.Result.Success;
                req.Dispose();

                if (!ok)
                {
                    if (File.Exists(temp)) try { File.Delete(temp); } catch { }
                    Changed?.Invoke();
                    onComplete?.Invoke(false);
                    return;
                }

                ImportLocal(packageId, temp, version, entry, onComplete, tempToDelete: temp);
            }

            EditorApplication.update += Poll;
        }

        static void SetDefineForPackage(NexusPackageCatalog.PackageEntry entry)
        {
            if (entry == null) return;
            switch (entry.id)
            {
                case "applovin_max": NexusAdsInstaller.SetDefine(true); break;
                case "levelplay": NexusLevelPlayInstaller.SetDefine(true); break;
                default:
                    var sdk = NexusInstaller.Sdks.Find(s => s.Id == entry.id);
                    if (sdk != null && !string.IsNullOrEmpty(sdk.Define))
                        NexusInstaller.SetDefine(sdk.Define, true);
                    break;
            }
        }

        public static void InstallOrUpdate(string packageId, Action<bool> onComplete = null)
        {
            var status = NexusPackageCatalog.GetStatus(packageId);
            if (status.UpdateAvailable)
                UpdatePackage(packageId, onComplete);
            else
                InstallFresh(packageId, onComplete);
        }

        static void InstallFresh(string packageId, Action<bool> onComplete)
        {
            switch (packageId)
            {
                case "applovin_max":
                    NexusAdsInstaller.Import();
                    onComplete?.Invoke(true);
                    return;
                case "levelplay":
                    NexusLevelPlayInstaller.Import();
                    onComplete?.Invoke(true);
                    return;
                case "unity_iap":
                    NexusIapInstaller.Install();
                    onComplete?.Invoke(true);
                    return;
                case "mobile_notifications":
                    NexusNotificationsInstaller.Install();
                    onComplete?.Invoke(true);
                    return;
                default:
                    var sdk = NexusInstaller.Sdks.Find(s => s.Id == packageId);
                    if (sdk != null)
                    {
                        NexusInstaller.Import(sdk);
                        onComplete?.Invoke(true);
                    }
                    else onComplete?.Invoke(false);
                    break;
            }
        }
    }
}
