using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;

namespace Davanci.Nexus.Editor
{
    // Describes a bundled SDK: how to import/remove it and toggle its define symbol.
    public class NexusSdk
    {
        public string Id;
        public string DisplayName;
        public string PackageFileName;    // file inside InstallerPackages/
        public string Define;             // scripting define symbol (null = none)
        public string DetectTypeName;     // type used to detect "installed"
        public string Description;
        public string[] InstallFolders;   // asset folders created by this package
        public string LogoAssetPath;      // logo shipped by the SDK (null = initials badge)
        public Color BrandColor;
        public string Initials;
        public string DownloadUrl;        // set when the package isn't bundled and must be fetched

        public bool IsInstalled =>
            !string.IsNullOrEmpty(DetectTypeName) && NexusInstaller.TypeExists(DetectTypeName);

        public bool DefineEnabled =>
            !string.IsNullOrEmpty(Define) && NexusInstaller.HasDefine(Define);

        // True for SDKs we ship as a .unitypackage in InstallerPackages/. GPGS isn't bundled
        // (Google distributes it themselves), so it carries a DownloadUrl instead.
        public bool PackageBundled =>
            !string.IsNullOrEmpty(PackageFileName) &&
            File.Exists(Path.Combine(NexusInstaller.PackagesFolder, PackageFileName));
    }

    public static class NexusInstaller
    {
        public const string PackagesFolder = "Assets/DavanciNexus/InstallerPackages";

        public static readonly List<NexusSdk> Sdks = new List<NexusSdk>
        {
            new NexusSdk
            {
                Id = "edm",
                DisplayName = "External Dependency Manager (EDM4U)",
                PackageFileName = "ExternalDependencyManager.unitypackage",
                Define = null,
                DetectTypeName = "Google.VersionHandler, Google.VersionHandler",
                Description = "Resolves iOS (CocoaPods) & Android (Gradle) native dependencies. Firebase bundles this too.",
                InstallFolders = new[] { "Assets/ExternalDependencyManager" },
                LogoAssetPath = "Assets/DavanciNexus/Art/Providers_Icons/EDM.png",
                BrandColor = new Color(0.45f, 0.47f, 0.52f),
                Initials = "EDM",
            },
            new NexusSdk
            {
                Id = "appsflyer",
                DisplayName = "AppsFlyer (attribution)",
                PackageFileName = "AppsFlyer.unitypackage",
                Define = "DAVANCI_APPSFLYER",
                DetectTypeName = "AppsFlyerSDK.AppsFlyer",
                Description = "Install attribution / MMP. Needs a Dev Key + Apple App ID.",
                InstallFolders = new[] { "Assets/AppsFlyer" },
                LogoAssetPath = "Assets/DavanciNexus/Art/Providers_Icons/appsflayer.png",
                BrandColor = new Color(0.10f, 0.61f, 0.99f),
                Initials = "AF",
            },
            new NexusSdk
            {
                Id = "ga",
                DisplayName = "GameAnalytics",
                PackageFileName = "GA_SDK_UNITY.unitypackage",
                Define = "DAVANCI_GA",
                DetectTypeName = "GameAnalyticsSDK.GameAnalytics",
                Description = "Free game analytics + the ATT prompt client used on iOS.",
                InstallFolders = new[] { "Assets/GameAnalytics" },
                LogoAssetPath = "Assets/DavanciNexus/Art/Providers_Icons/Gameanalytics.png",
                BrandColor = new Color(0.90f, 0.0f, 0.35f),
                Initials = "GA",
            },
            new NexusSdk
            {
                Id = "firebase",
                DisplayName = "Firebase Analytics",
                PackageFileName = "FirebaseAnalytics.unitypackage",
                Define = "DAVANCI_FIREBASE",
                DetectTypeName = "Firebase.Analytics.FirebaseAnalytics",
                Description = "Google Analytics for Firebase (free Spark plan is enough). Bundles EDM4U + FirebaseApp.",
                // Firebase scatters native static libs outside Assets/Firebase — these MUST be
                // deleted on Remove or they keep referencing FIRAnalytics/FIRApp at link time
                // (Undefined symbol) even after the C# SDK is gone.
                InstallFolders = new[]
                {
                    "Assets/Firebase",
                    "Assets/Plugins/iOS/Firebase",
                    "Assets/Plugins/tvOS/Firebase",
                    "Assets/Plugins/Android/Firebase",
                    "Assets/Editor Default Resources/Firebase",
                    "Assets/ExternalDependencyManager",
                },
                LogoAssetPath = "Assets/DavanciNexus/Art/Providers_Icons/firebase.png",
                BrandColor = new Color(1.0f, 0.63f, 0.0f),
                Initials = "FB",
            },
            new NexusSdk
            {
                Id = "gpgs",
                DisplayName = "Google Play Games (Android Cloud Save)",
                PackageFileName = "GooglePlayGamesPlugin-2.3.0.unitypackage",
                Define = "DAVANCI_GPGS",
                DetectTypeName = "GooglePlayGames.PlayGamesPlatform, GooglePlayGames",
                Description = "Saved Games cloud slot for Android, the iCloud counterpart. After importing, finish setup in Nexus Setup → Play Games.",
                InstallFolders = new[] { "Assets/GooglePlayGames", "Assets/ExternalDependencyManager" },
                LogoAssetPath = "Assets/DavanciNexus/Art/Providers_Icons/google-play-games.png",
                BrandColor = new Color(0.20f, 0.66f, 0.33f),
                Initials = "PG",
                DownloadUrl = "https://github.com/playgameservices/play-games-plugin-for-unity/releases/download/v2.3.0/GooglePlayGamesPlugin-2.3.0.unitypackage",
            },
        };

        public static void Import(NexusSdk sdk)
        {
            string path = Path.Combine(PackagesFolder, sdk.PackageFileName);
            if (!File.Exists(path))
            {
                // Non-bundled SDK (e.g. GPGS): send the user to the download, don't set the
                // define yet — the define would make Nexus reference a plugin that isn't there.
                if (!string.IsNullOrEmpty(sdk.DownloadUrl))
                {
                    if (EditorUtility.DisplayDialog("DavanciNexus",
                            $"{sdk.DisplayName} isn't bundled with Nexus. Open Google's download page?\n\n" +
                            "After it downloads, import it via Assets → Import Package → Custom Package, " +
                            "then come back and press Enable.",
                            "Open download", "Cancel"))
                        Application.OpenURL(sdk.DownloadUrl);
                    return;
                }
                EditorUtility.DisplayDialog("DavanciNexus", $"Package not found:\n{path}", "OK");
                return;
            }

            if (!string.IsNullOrEmpty(sdk.Define))
                SetDefine(sdk.Define, true);

            NexusPackageUpdater.ImportTracked(sdk.Id, path, NexusPackageCatalog.Get(sdk.Id)?.version);
        }

        public static void ImportAll()
        {
            int count = 0;
            foreach (var sdk in Sdks)
            {
                if (sdk.IsInstalled) continue;
                string path = Path.Combine(PackagesFolder, sdk.PackageFileName);
                if (!File.Exists(path)) continue;
                if (!string.IsNullOrEmpty(sdk.Define))
                    SetDefine(sdk.Define, true);
                AssetDatabase.ImportPackage(path, false);
                count++;
            }

            if (count == 0)
            {
                EditorUtility.DisplayDialog("DavanciNexus",
                    "All bundled SDK packages are already installed.", "OK");
                return;
            }

            EditorUtility.DisplayDialog("DavanciNexus",
                $"Importing {count} SDK package(s). Let Unity finish compiling, then return to Nexus Setup.", "OK");
        }

        public static void Remove(NexusSdk sdk)
        {
            if (sdk.InstallFolders != null)
            {
                foreach (var folder in sdk.InstallFolders)
                {
                    if (AssetDatabase.IsValidFolder(folder))
                        AssetDatabase.DeleteAsset(folder);
                }
            }
            if (!string.IsNullOrEmpty(sdk.Define))
                SetDefine(sdk.Define, false);

            AssetDatabase.Refresh();
        }

        public static void RemoveAll()
        {
            bool ok = EditorUtility.DisplayDialog("DavanciNexus",
                "Remove ALL installed SDK packages (AppsFlyer, GameAnalytics, Firebase, Google Play Games, EDM4U) and clear their define symbols?\n\n" +
                "Your GoogleService-Info.plist / google-services.json are left untouched.",
                "Remove everything", "Cancel");
            if (!ok) return;

            foreach (var sdk in Sdks)
                Remove(sdk);

            EditorUtility.DisplayDialog("DavanciNexus",
                "Removed. Unity will recompile. Some leftover empty folders may remain. Safe to delete manually.", "OK");
        }

        public const string FirebaseIosFile = "Assets/GoogleService-Info.plist";
        public const string FirebaseAndroidFile = "Assets/google-services.json";

        public static bool FirebaseIosConfigPresent => File.Exists(FirebaseIosFile);
        public static bool FirebaseAndroidConfigPresent => File.Exists(FirebaseAndroidFile);

        public static void SwitchPlatform(BuildTarget target)
        {
            var group = BuildPipeline.GetBuildTargetGroup(target);
            EditorUserBuildSettings.SwitchActiveBuildTarget(group, target);
        }

        public static BuildTarget ActiveBuildTarget => EditorUserBuildSettings.activeBuildTarget;

        static NamedBuildTarget CurrentNamedTarget =>
            NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);

        public static bool HasDefine(string define)
        {
            return HasDefineOnTarget(CurrentNamedTarget, define);
        }

        static bool HasDefineOnTarget(NamedBuildTarget target, string define)
        {
            string defines = PlayerSettings.GetScriptingDefineSymbols(target);
            return defines.Split(';').Any(d => d.Trim() == define);
        }

        static readonly NamedBuildTarget[] DefineTargets =
        {
            NamedBuildTarget.Standalone,
            NamedBuildTarget.iOS,
            NamedBuildTarget.Android,
            NamedBuildTarget.tvOS,
        };

        // Drop DAVANCI_* defines when the SDK folders were removed outside Nexus Installer.
        // Returns how many defines were cleared.
        public static int SyncDefines()
        {
            int cleared = 0;
            foreach (var sdk in Sdks)
            {
                if (string.IsNullOrEmpty(sdk.Define)) continue;
                if (!sdk.IsInstalled && HasDefineOnAnyTarget(sdk.Define))
                {
                    SetDefine(sdk.Define, false);
                    cleared++;
                }
            }
            return cleared;
        }

        static bool HasDefineOnAnyTarget(string define)
        {
            foreach (var target in DefineTargets)
                if (HasDefineOnTarget(target, define)) return true;
            return false;
        }

        public static IEnumerable<NexusSdk> StaleDefineSdks()
        {
            foreach (var sdk in Sdks)
            {
                if (string.IsNullOrEmpty(sdk.Define)) continue;
                if (!sdk.IsInstalled && HasDefineOnAnyTarget(sdk.Define))
                    yield return sdk;
            }
        }

        public static void SetDefine(string define, bool enabled)
        {
            if (string.IsNullOrEmpty(define)) return;
            foreach (var target in DefineTargets)
                ApplyDefine(target, define, enabled);
        }

        static void ApplyDefine(NamedBuildTarget target, string define, bool enabled)
        {
            string raw = PlayerSettings.GetScriptingDefineSymbols(target);
            var list = raw.Split(';').Select(d => d.Trim()).Where(d => d.Length > 0).ToList();
            bool present = list.Contains(define);

            if (enabled && !present) list.Add(define);
            else if (!enabled && present) list.Remove(define);
            else return;

            PlayerSettings.SetScriptingDefineSymbols(target, string.Join(";", list));
        }

        public static bool TypeExists(string typeName)
        {
            if (Type.GetType(typeName) != null) return true;
            string shortName = typeName.Split(',')[0].Trim();
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (asm.GetType(shortName) != null) return true;
                // Short-name scan so namespaced types (e.g. com.unity3d.mediation.LevelPlay) are
                // still found when only the class name is given.
                Type[] types;
                try { types = asm.GetTypes(); }
                catch (System.Reflection.ReflectionTypeLoadException e) { types = e.Types; }
                catch { continue; }
                foreach (var t in types)
                    if (t != null && t.Name == shortName) return true;
            }
            return false;
        }
    }
}
