using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using Davanci.Nexus;

namespace Davanci.Nexus.Editor
{
    // Zero-config in-editor updater for consuming games. Once a day it reads the public
    // manifest { version, url, notes } and offers a one-click download + import when a
    // newer version than NexusVersion.Current is published.
    [InitializeOnLoad]
    public static class NexusUpdater
    {
        // Manifest source; falls back to "<NexusConfig.realtimeDbUrl>/nexus_update.json" when empty.
        const string ManifestUrl = "https://davancinexus-default-rtdb.firebaseio.com/nexus_update.json";
        const string ManifestNode = "/nexus_update.json";
        const double CheckEveryHours = 24;
        const string AuthorMarkerFile = ".davanci-nexus-source";

        // Per-project so a check in one game doesn't suppress another's daily check.
        static string KeyLastCheck => "Davanci.Nexus.Updater.LastCheck." + Application.dataPath.GetHashCode();

        [Serializable]
        class Manifest
        {
            public string version;
            public string url;
            public string notes;
            public RemotePackageEntry[] packages;
        }

        [Serializable]
        public class RemotePackageEntry
        {
            public string id;
            public string version;
            public string url;
        }

        const string PackagesManifestNode = "/nexus_packages.json";

        // Result of a silent update probe, consumed by the setup window and the startup check.
        public struct UpdateInfo
        {
            public bool Available;     // true only when the manifest version is newer than Current
            public string Version;     // latest published version
            public string Url;         // .unitypackage download URL
            public string Notes;       // release notes
        }

        static NexusUpdater() => EditorApplication.delayCall += AutoCheck;

        [MenuItem("Davanci Nexus/Check for Updates")]
        public static void CheckMenu() => CheckForUpdates(silent: false);

        // Runs once per Unity session (throttled to once a day) on editor load. When a newer
        // version exists it routes the user into the setup wizard's Update step.
        // True only in the authoring project (marker at project root, never shipped).
        internal static bool IsAuthoringProject()
        {
            string root = Directory.GetParent(Application.dataPath)?.FullName;
            return root != null && File.Exists(Path.Combine(root, AuthorMarkerFile));
        }

        static void AutoCheck()
        {
            if (IsAuthoringProject()) return;
            if (!DueForCheck()) return;

            EditorPrefs.SetString(KeyLastCheck, DateTime.UtcNow.ToString("o"));
            GetUpdateInfo(info =>
            {
                if (!info.Available) return;

                string body = $"DavanciNexus v{info.Version} is available. You have v{NexusVersion.Current}.";
                if (!string.IsNullOrEmpty(info.Notes)) body += "\n\n" + info.Notes;

                if (EditorUtility.DisplayDialog("DavanciNexus update available", body, "Open updater", "Later"))
                    NexusSetupWindow.Open();
            });
        }

        // Silent, non-blocking update probe. Always invokes done, with Available=false when
        // up to date, unreachable, or the manifest is missing/malformed.
        public static void GetUpdateInfo(Action<UpdateInfo> done)
        {
            string url = ResolveManifestUrl();
            if (string.IsNullOrEmpty(url)) { done(default); return; }

            FetchText(url, (ok, text) =>
            {
                var info = new UpdateInfo();
                if (ok && !string.IsNullOrEmpty(text) && text.Trim() != "null")
                {
                    Manifest m = null;
                    try { m = JsonUtility.FromJson<Manifest>(text); } catch { /* ignored */ }

                    if (m != null && !string.IsNullOrEmpty(m.version) && !string.IsNullOrEmpty(m.url)
                        && CompareVersions(m.version, NexusVersion.Current) > 0)
                    {
                        info.Available = true;
                        info.Version = m.version;
                        info.Url = m.url;
                        info.Notes = m.notes;
                    }
                }
                done(info);
            });
        }

        // Public entry for UI that already knows the target version/URL (e.g. the setup wizard).
        public static void DownloadUpdate(string version, string url, Action<bool> onComplete = null) =>
            Download(new Manifest { version = version, url = url }, onComplete);

        public static void CheckForUpdates(bool silent)
        {
            string url = ResolveManifestUrl();
            if (string.IsNullOrEmpty(url))
            {
                if (!silent)
                    EditorUtility.DisplayDialog("DavanciNexus",
                        "No manifest URL configured and NexusConfig.realtimeDbUrl is empty.", "OK");
                return;
            }

            EditorPrefs.SetString(KeyLastCheck, DateTime.UtcNow.ToString("o"));

            FetchText(url, (ok, text) =>
            {
                if (!ok || string.IsNullOrEmpty(text) || text.Trim() == "null")
                {
                    if (!silent)
                        EditorUtility.DisplayDialog("DavanciNexus",
                            "No update manifest found (or the server couldn't be reached).", "OK");
                    return;
                }

                Manifest m = null;
                try { m = JsonUtility.FromJson<Manifest>(text); } catch { /* ignored */ }

                if (m == null || string.IsNullOrEmpty(m.version) || string.IsNullOrEmpty(m.url))
                {
                    if (!silent)
                        EditorUtility.DisplayDialog("DavanciNexus", "Update manifest is malformed.", "OK");
                    return;
                }

                if (CompareVersions(m.version, NexusVersion.Current) <= 0)
                {
                    if (!silent)
                        EditorUtility.DisplayDialog("DavanciNexus",
                            $"You're up to date (v{NexusVersion.Current}).", "OK");
                    return;
                }

                string body = $"Version {m.version} is available. You have {NexusVersion.Current}.";
                if (!string.IsNullOrEmpty(m.notes)) body += "\n\n" + m.notes;

                if (EditorUtility.DisplayDialog("DavanciNexus update available", body, "Update now", "Later"))
                    Download(m);
            });
        }

        static string ResolveManifestUrl()
        {
            if (!string.IsNullOrEmpty(ManifestUrl)) return ManifestUrl;
            string baseUrl = NexusConfig.Load()?.realtimeDbUrl;
            return string.IsNullOrEmpty(baseUrl) ? null : baseUrl.TrimEnd('/') + ManifestNode;
        }

        static void Download(Manifest m, Action<bool> onComplete = null)
        {
            string temp = Path.Combine(Path.GetTempPath(), $"DavanciNexus_{m.version}.unitypackage");

            var req = UnityWebRequest.Get(m.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 v{m.version}…", req.downloadProgress))
                    req.Abort();

                if (!op.isDone) return;

                EditorApplication.update -= Poll;
                EditorUtility.ClearProgressBar();

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

                if (!ok)
                {
                    if (File.Exists(temp)) try { File.Delete(temp); } catch { }
                    EditorUtility.DisplayDialog("DavanciNexus", "Download failed or was cancelled.", "OK");
                    onComplete?.Invoke(false);
                    return;
                }

                void FinishImport(bool success)
                {
                    AssetDatabase.importPackageCompleted -= OnImported;
                    AssetDatabase.importPackageCancelled -= OnCancelled;
                    if (success) Debug.Log($"[Nexus] Imported update v{m.version}.");
                    onComplete?.Invoke(success);
                }

                void OnImported(string _) => FinishImport(true);
                void OnCancelled(string _) => FinishImport(false);

                AssetDatabase.importPackageCompleted += OnImported;
                AssetDatabase.importPackageCancelled += OnCancelled;
                AssetDatabase.ImportPackage(temp, false);
            }

            EditorApplication.update += Poll;
        }

        static void FetchText(string url, Action<bool, string> done)
        {
            var req = UnityWebRequest.Get(url);
            var op = req.SendWebRequest();

            void Poll()
            {
                if (!op.isDone) return;
                EditorApplication.update -= Poll;
                bool ok = req.result == UnityWebRequest.Result.Success;
                string txt = ok ? req.downloadHandler.text : null;
                req.Dispose();
                done(ok, txt);
            }

            EditorApplication.update += Poll;
        }

        static bool DueForCheck()
        {
            string last = EditorPrefs.GetString(KeyLastCheck, "");
            if (!string.IsNullOrEmpty(last) &&
                DateTime.TryParse(last, null, System.Globalization.DateTimeStyles.RoundtripKind, out var when) &&
                (DateTime.UtcNow - when).TotalHours < CheckEveryHours)
                return false;
            return true;
        }

        public static bool IsAtLeastVersion(string installed, string target) =>
            CompareVersions(installed, target) >= 0;

        static Dictionary<string, RemotePackageEntry> _cachedRemotePackages;

        // Merged remote package updates from nexus_update.json (packages[]) and nexus_packages.json.
        public static void GetRemotePackages(Action<Dictionary<string, RemotePackageEntry>> done)
        {
            if (_cachedRemotePackages != null)
            {
                done(new Dictionary<string, RemotePackageEntry>(_cachedRemotePackages));
                return;
            }

            var merged = new Dictionary<string, RemotePackageEntry>();
            string mainUrl = ResolveManifestUrl();
            string pkgUrl = ResolvePackagesManifestUrl();
            int pending = 0;
            if (!string.IsNullOrEmpty(mainUrl)) pending++;
            if (!string.IsNullOrEmpty(pkgUrl)) pending++;

            if (pending == 0)
            {
                done(merged);
                return;
            }

            void FinishOne()
            {
                pending--;
                if (pending > 0) return;
                _cachedRemotePackages = merged;
                done(new Dictionary<string, RemotePackageEntry>(merged));
            }

            if (!string.IsNullOrEmpty(mainUrl))
            {
                FetchText(mainUrl, (ok, text) =>
                {
                    if (ok && !string.IsNullOrEmpty(text) && text.Trim() != "null")
                    {
                        try
                        {
                            var m = JsonUtility.FromJson<Manifest>(text);
                            MergePackages(merged, m?.packages);
                        }
                        catch { /* ignored */ }
                    }
                    FinishOne();
                });
            }

            if (!string.IsNullOrEmpty(pkgUrl))
            {
                FetchText(pkgUrl, (ok, text) =>
                {
                    if (ok && !string.IsNullOrEmpty(text) && text.Trim() != "null")
                    {
                        try
                        {
                            var wrapper = JsonUtility.FromJson<PackageManifestFile>(text);
                            MergePackages(merged, wrapper?.packages);
                        }
                        catch { /* ignored */ }
                    }
                    FinishOne();
                });
            }
        }

        [Serializable]
        class PackageManifestFile
        {
            public RemotePackageEntry[] packages;
        }

        static void MergePackages(Dictionary<string, RemotePackageEntry> merged, RemotePackageEntry[] entries)
        {
            if (entries == null) return;
            foreach (var e in entries)
            {
                if (e == null || string.IsNullOrEmpty(e.id) || string.IsNullOrEmpty(e.version)) continue;
                if (merged.TryGetValue(e.id, out var existing)
                    && CompareVersions(e.version, existing.version) <= 0)
                    continue;
                merged[e.id] = e;
            }
        }

        static string ResolvePackagesManifestUrl()
        {
            if (!string.IsNullOrEmpty(ManifestUrl))
                return ManifestUrl.Replace("/nexus_update.json", PackagesManifestNode);
            string baseUrl = NexusConfig.Load()?.realtimeDbUrl;
            return string.IsNullOrEmpty(baseUrl) ? null : baseUrl.TrimEnd('/') + PackagesManifestNode;
        }

        public static void ClearRemotePackageCache() => _cachedRemotePackages = null;

        public static int CompareVersions(string a, string b)
        {
            int[] pa = Parse(a), pb = Parse(b);
            for (int i = 0; i < 3; i++)
                if (pa[i] != pb[i]) return pa[i].CompareTo(pb[i]);
            return 0;
        }

        static int[] Parse(string v)
        {
            var n = new int[3];
            if (string.IsNullOrEmpty(v)) return n;
            string[] parts = v.Trim().TrimStart('v', 'V').Split('.');
            for (int i = 0; i < 3 && i < parts.Length; i++) int.TryParse(parts[i], out n[i]);
            return n;
        }
    }
}
