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

namespace Davanci.Nexus.Editor
{
    // Bundled + installed package versions. NexusPackageVersions.json ships with each DavanciNexus
    // release; NexusInstalledPackages.json records what was actually imported in this project.
    public static class NexusPackageCatalog
    {
        const string CatalogPath = "Assets/DavanciNexus/Editor/NexusPackageVersions.json";
        const string InstalledPath = "Assets/DavanciNexus/Editor/NexusInstalledPackages.json";

        [Serializable]
        public class PackageEntry
        {
            public string id;
            public string displayName;
            public string version;
            public string kind;          // unitypackage | upm
            public string file;
            public string filePattern;
            public string upmId;
        }

        [Serializable]
        class CatalogFile
        {
            public string nexusVersion;
            public PackageEntry[] packages;
        }

        [Serializable]
        class InstalledFile
        {
            public InstalledEntry[] packages;
        }

        [Serializable]
        public class InstalledEntry
        {
            public string id;
            public string version;
        }

        public struct PackageStatus
        {
            public PackageEntry Entry;
            public bool Installed;
            public string InstalledVersion;
            public string BundledVersion;
            public string RemoteVersion;
            public string TargetVersion;
            public bool UpdateAvailable;
            public string LocalPackagePath;
            public string RemoteUrl;
        }

        static CatalogFile _catalog;
        static InstalledFile _installed;
        static Dictionary<string, NexusUpdater.RemotePackageEntry> _remote;

        public static IReadOnlyList<PackageEntry> All => LoadCatalog().packages ?? Array.Empty<PackageEntry>();

        public static PackageEntry Get(string id) =>
            All.FirstOrDefault(p => p.id == id);

        public static void SetRemote(Dictionary<string, NexusUpdater.RemotePackageEntry> remote) =>
            _remote = remote;

        public static PackageStatus GetStatus(string id)
        {
            var entry = Get(id);
            var status = new PackageStatus { Entry = entry };
            if (entry == null) return status;

            status.BundledVersion = entry.version;
            status.InstalledVersion = ResolveInstalledVersion(entry);
            status.Installed = IsPackageInstalled(entry);
            status.LocalPackagePath = ResolveBundledPath(entry);

            if (_remote != null && _remote.TryGetValue(id, out var remote))
            {
                status.RemoteVersion = remote.version;
                status.RemoteUrl = remote.url;
            }

            status.TargetVersion = PickTargetVersion(status.BundledVersion, status.RemoteVersion);

            // Unknown installed version (legacy import before tracking): don't treat bundled as
            // an update — only flag when a remote manifest publishes something newer.
            if (status.Installed && string.IsNullOrEmpty(status.InstalledVersion))
            {
                status.UpdateAvailable = !string.IsNullOrEmpty(status.RemoteVersion)
                    && !string.IsNullOrEmpty(status.BundledVersion)
                    && NexusUpdater.CompareVersions(status.RemoteVersion, status.BundledVersion) > 0;
            }
            else
            {
                status.UpdateAvailable = status.Installed
                    && !string.IsNullOrEmpty(status.TargetVersion)
                    && !string.IsNullOrEmpty(status.InstalledVersion)
                    && NexusUpdater.CompareVersions(status.TargetVersion, status.InstalledVersion) > 0;
            }

            return status;
        }

        // One-time fix for projects that already had SDKs before version tracking shipped.
        public static int ReconcileLegacyInstalls()
        {
            int stamped = 0;
            foreach (var entry in All)
            {
                if (!IsPackageInstalled(entry)) continue;
                if (!string.IsNullOrEmpty(GetInstalledVersion(entry.id))) continue;
                string version = entry.kind == "upm"
                    ? ReadUpmVersion(entry.upmId)
                    : entry.version;
                if (string.IsNullOrEmpty(version)) continue;
                StampInstalled(entry.id, version);
                stamped++;
            }
            return stamped;
        }

        public static void StampInstalled(string id, string version)
        {
            if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(version)) return;
            var data = LoadInstalled();
            var list = data.packages?.ToList() ?? new List<InstalledEntry>();
            list.RemoveAll(p => p.id == id);
            list.Add(new InstalledEntry { id = id, version = version });
            data.packages = list.ToArray();
            SaveInstalled(data);
        }

        public static string GetInstalledVersion(string id)
        {
            var entry = LoadInstalled().packages?.FirstOrDefault(p => p.id == id);
            return entry?.version;
        }

        public static bool IsPackageInstalled(PackageEntry entry)
        {
            if (entry == null) return false;
            if (entry.kind == "upm")
                return !string.IsNullOrEmpty(entry.upmId) && ReadUpmVersion(entry.upmId) != null;
            return ResolveInstalledFlag(entry);
        }

        static string ResolveInstalledVersion(PackageEntry entry)
        {
            string stamped = GetInstalledVersion(entry.id);
            if (!string.IsNullOrEmpty(stamped)) return stamped;
            if (entry.kind == "upm")
                return ReadUpmVersion(entry.upmId);
            return null;
        }

        static bool ResolveInstalledFlag(PackageEntry entry)
        {
            switch (entry.id)
            {
                case "applovin_max": return NexusAdsInstaller.Installed;
                case "levelplay": return NexusLevelPlayInstaller.Installed;
                case "unity_iap": return NexusIapInstaller.Installed;
                case "mobile_notifications": return NexusNotificationsInstaller.Installed;
                default:
                    var sdk = NexusInstaller.Sdks.FirstOrDefault(s => s.Id == entry.id);
                    return sdk != null && sdk.IsInstalled;
            }
        }

        static string PickTargetVersion(string bundled, string remote)
        {
            if (string.IsNullOrEmpty(bundled)) return remote;
            if (string.IsNullOrEmpty(remote)) return bundled;
            return NexusUpdater.CompareVersions(remote, bundled) > 0 ? remote : bundled;
        }

        public static string ResolveBundledPath(PackageEntry entry)
        {
            if (entry == null || entry.kind != "unitypackage") return null;
            string folder = NexusInstaller.PackagesFolder;
            if (!Directory.Exists(folder)) return null;

            if (!string.IsNullOrEmpty(entry.file))
            {
                string path = Path.Combine(folder, entry.file);
                return File.Exists(path) ? path : null;
            }

            if (!string.IsNullOrEmpty(entry.filePattern))
            {
                string pattern = entry.filePattern.Replace("*", "");
                return Directory.GetFiles(folder, entry.filePattern)
                    .OrderByDescending(f => f)
                    .FirstOrDefault();
            }

            return null;
        }

        static string ReadUpmVersion(string upmId)
        {
            try
            {
                string manifestPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName,
                    "Packages", "manifest.json");
                if (!File.Exists(manifestPath)) return null;
                string text = File.ReadAllText(manifestPath);
                string needle = $"\"{upmId}\": \"";
                int idx = text.IndexOf(needle, StringComparison.Ordinal);
                if (idx < 0) return null;
                idx += needle.Length;
                int end = text.IndexOf('"', idx);
                return end > idx ? text.Substring(idx, end - idx) : null;
            }
            catch { return null; }
        }

        static CatalogFile LoadCatalog()
        {
            if (_catalog != null) return _catalog;
            _catalog = new CatalogFile { packages = Array.Empty<PackageEntry>() };
            if (!File.Exists(CatalogPath)) return _catalog;
            try { _catalog = JsonUtility.FromJson<CatalogFile>(File.ReadAllText(CatalogPath)) ?? _catalog; }
            catch { /* keep empty */ }
            if (_catalog.packages == null) _catalog.packages = Array.Empty<PackageEntry>();
            return _catalog;
        }

        static InstalledFile LoadInstalled()
        {
            if (_installed != null) return _installed;
            _installed = new InstalledFile { packages = Array.Empty<InstalledEntry>() };
            if (!File.Exists(InstalledPath)) return _installed;
            try { _installed = JsonUtility.FromJson<InstalledFile>(File.ReadAllText(InstalledPath)) ?? _installed; }
            catch { /* keep empty */ }
            if (_installed.packages == null) _installed.packages = Array.Empty<InstalledEntry>();
            return _installed;
        }

        static void SaveInstalled(InstalledFile data)
        {
            _installed = data;
            File.WriteAllText(InstalledPath, JsonUtility.ToJson(data, true));
            AssetDatabase.Refresh();
        }

        public static void Reload()
        {
            _catalog = null;
            _installed = null;
        }

        [MenuItem("Davanci Nexus/Reconcile installed package versions")]
        public static void ReconcileMenu()
        {
            int n = ReconcileLegacyInstalls();
            EditorUtility.DisplayDialog("DavanciNexus",
                n > 0
                    ? $"Stamped {n} installed package version(s). Open Nexus Setup → Install SDKs to review."
                    : "All installed packages already have a recorded version (or none are installed).",
                "OK");
            NexusSetupWindow.RefreshIfOpen();
        }

        [MenuItem("Davanci Nexus/Sync package version catalog")]
        public static void SyncCatalogFromFolder()
        {
            if (!NexusUpdater.IsAuthoringProject())
            {
                EditorUtility.DisplayDialog("DavanciNexus",
                    "Catalog sync is for the authoring project only.", "OK");
                return;
            }

            var catalog = LoadCatalog();
            var list = catalog.packages?.ToList() ?? new List<PackageEntry>();
            string folder = NexusInstaller.PackagesFolder;
            if (!Directory.Exists(folder))
            {
                EditorUtility.DisplayDialog("DavanciNexus", "InstallerPackages folder not found.", "OK");
                return;
            }

            foreach (var entry in list.Where(p => p.kind == "unitypackage"))
            {
                string path = ResolveBundledPath(entry);
                if (string.IsNullOrEmpty(path)) continue;
                string inferred = InferVersionFromFileName(Path.GetFileName(path));
                if (!string.IsNullOrEmpty(inferred))
                    entry.version = inferred;
                if (!string.IsNullOrEmpty(entry.filePattern))
                    entry.file = Path.GetFileName(path);
            }

            catalog.nexusVersion = NexusVersion.Current;
            catalog.packages = list.ToArray();
            File.WriteAllText(CatalogPath, JsonUtility.ToJson(catalog, true));
            _catalog = catalog;
            AssetDatabase.Refresh();
            Debug.Log("[Nexus] NexusPackageVersions.json synced from InstallerPackages.");
        }

        static string InferVersionFromFileName(string fileName)
        {
            if (string.IsNullOrEmpty(fileName)) return null;
            // UnityLevelPlay_v9.4.1.unitypackage, GooglePlayGamesPlugin-2.1.0.unitypackage
            var match = System.Text.RegularExpressions.Regex.Match(fileName,
                @"(?:v|Plugin-)(\d+\.\d+(?:\.\d+)?)|(\d+\.\d+(?:\.\d+)?)-Android");
            if (match.Success)
            {
                for (int i = 1; i < match.Groups.Count; i++)
                    if (match.Groups[i].Success && !string.IsNullOrEmpty(match.Groups[i].Value))
                        return match.Groups[i].Value;
            }
            return null;
        }
    }
}
