using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;

namespace CoolGameX.Setup
{
    /// <summary>Tools › Cool GameX › Setup — everything needed to wire a game in.</summary>
    public class CoolGameXSetupWindow : EditorWindow
    {
        private const string SettingsFolder = "Assets/Resources";
        private const string SettingsPath = SettingsFolder + "/" + CoolGameXSettings.ResourceName + ".asset";
        private const string DocsUrl = "https://coolgamex.com/sdk.html";
        private const string SiteUrl = "https://coolgamex.com";
        private const string DiscordUrl = "https://discord.gg/Qea9Dfmex2";

        [Serializable] private class LoginReq { public string email; public string password; public string gameSlug; }
        [Serializable] private class LoginUser { public string id; public string email; public string username; }
        [Serializable] private class LoginResp { public string token; public long expiresAt; public string gameSlug; public LoginUser user; }
        [Serializable] private class RefreshResp { public string token; public long expiresAt; }
        [Serializable] private class CatalogItem { public string slug; public string name; public string description; public int points; }
        [Serializable] private class Catalog { public CatalogItem[] achievements; public CatalogItem[] dlc; }
        [Serializable] private class UnlockReq { public string gameSlug; public string achievementSlug; }
        [Serializable] private class UnlockResp { public bool newlyUnlocked; }

        private CoolGameXSettings settings;
        private SerializedObject so;
        private EditorSession.Data session;
        private string email = "";
        private string password = "";
        private bool busy;
        private string note;
        private MessageType noteType = MessageType.None;
        private string connectionText;
        private bool? connectionOk;
        private Catalog catalog;
        private string catalogSlug;
        private Vector2 scroll;
        private bool showOptions = true;

        [MenuItem("Tools/Cool GameX/Setup", priority = 0)]
        public static void Open()
        {
            var w = GetWindow<CoolGameXSetupWindow>();
            w.titleContent = new GUIContent("Cool GameX");
            w.minSize = new Vector2(460, 540);
            w.Show();
        }

        [MenuItem("Tools/Cool GameX/Generate IDs", priority = 11)]
        private static void GenerateFromMenu()
        {
            Open();
            _ = GetWindow<CoolGameXSetupWindow>().GenerateIds();
        }

        [MenuItem("Tools/Cool GameX/Documentation", priority = 30)]
        private static void OpenDocs() => Application.OpenURL(DocsUrl);

        private void OnEnable()
        {
            settings = FindSettings();
            session = EditorSession.Load();
            _ = MaybeRefreshSession();
        }

        private void OnFocus()
        {
            if (settings == null) settings = FindSettings();
            session = EditorSession.Load();
        }

        private string Slug => settings != null ? (settings.gameSlug ?? "").Trim() : "";
        private string Server => settings != null && !string.IsNullOrWhiteSpace(settings.serverUrl)
            ? settings.serverUrl.Trim().TrimEnd('/')
            : CoolGameXSettings.DefaultServerUrl;
        private bool SignedIn => EditorSession.IsValid(session) && string.Equals(session.gameSlug, Slug, StringComparison.OrdinalIgnoreCase);

        // ------------------------------------------------------------------
        //  Drawing
        // ------------------------------------------------------------------

        private void OnGUI()
        {
            Styles.Ensure();
            scroll = EditorGUILayout.BeginScrollView(scroll);
            GUILayout.BeginVertical(Styles.Page);

            DrawHeader();

            if (settings == null)
            {
                DrawCreate();
            }
            else
            {
                if (so == null || so.targetObject != settings) so = new SerializedObject(settings);
                so.Update();
                DrawGame();
                DrawSignIn();
                DrawCatalog();
                DrawOptions();
                so.ApplyModifiedProperties();
            }

            DrawFooter();
            GUILayout.EndVertical();
            EditorGUILayout.EndScrollView();
        }

        private void DrawHeader()
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label("Cool GameX", Styles.Title);
            GUILayout.FlexibleSpace();
            GUILayout.Label("SDK " + CoolAPI.Version, Styles.Muted);
            GUILayout.EndHorizontal();
            GUILayout.Label("Achievements, stats, leaderboards, cloud saves and DLC for your game.", Styles.Muted);
            EditorGUILayout.Space(8);

            GUILayout.BeginHorizontal();
            Check("Settings", settings != null);
            Check("Game slug", !string.IsNullOrEmpty(Slug));
            Check("Signed in", SignedIn);
            Check("IDs", File.Exists(IdGenerator.DefaultPath));
            GUILayout.FlexibleSpace();
            GUILayout.EndHorizontal();

            if (!string.IsNullOrEmpty(note))
            {
                EditorGUILayout.Space(6);
                EditorGUILayout.HelpBox(note, noteType);
            }
        }

        private void DrawCreate()
        {
            Section("Get started");
            EditorGUILayout.HelpBox(
                "Create the settings asset to begin. It holds your game slug and options (no secrets) " +
                "and lets the SDK start itself — no code or scene object needed.", MessageType.Info);
            if (GUILayout.Button("Create settings", GUILayout.Height(30)))
            {
                settings = CreateSettings();
                Selection.activeObject = settings;
            }
        }

        private void DrawGame()
        {
            Section("1   Your game");
            EditorGUILayout.PropertyField(so.FindProperty("gameSlug"), new GUIContent("Game slug", "Exactly as shown in the Creator dashboard, e.g. my-cool-game."));
            EditorGUILayout.PropertyField(so.FindProperty("serverUrl"), new GUIContent("Server URL", "When the game is started from the launcher, the launcher's server is used automatically."));

            string path = AssetDatabase.GetAssetPath(settings);
            if (!path.Replace('\\', '/').Contains("/Resources/") || Path.GetFileNameWithoutExtension(path) != CoolGameXSettings.ResourceName)
                EditorGUILayout.HelpBox($"Auto-start looks for Resources/{CoolGameXSettings.ResourceName}. Move this asset to {SettingsPath}.", MessageType.Warning);

            EditorGUILayout.Space(4);
            GUILayout.BeginHorizontal();
            using (new EditorGUI.DisabledScope(busy))
                if (GUILayout.Button("Test connection", GUILayout.Width(130))) _ = TestConnection();
            if (connectionOk.HasValue) GUILayout.Label(connectionText, connectionOk.Value ? Styles.Good : Styles.Bad);
            GUILayout.EndHorizontal();
        }

        private void DrawSignIn()
        {
            Section("2   Sign in for testing");
            if (EditorSession.IsValid(session))
            {
                string who = string.IsNullOrEmpty(session.username) ? session.email : $"{session.username}  ({session.email})";
                EditorGUILayout.LabelField("Signed in as", who);
                EditorGUILayout.LabelField("Game", session.gameSlug);
                EditorGUILayout.LabelField("Session", Expiry(session.expiresAt));
                if (!string.Equals(session.gameSlug, Slug, StringComparison.OrdinalIgnoreCase))
                    EditorGUILayout.HelpBox($"This sign-in is for \"{session.gameSlug}\" but your slug is \"{Slug}\". Sign out and sign in again.", MessageType.Warning);

                GUILayout.BeginHorizontal();
                using (new EditorGUI.DisabledScope(busy))
                {
                    if (GUILayout.Button("Refresh session", GUILayout.Width(130))) _ = RefreshSession(true);
                    if (GUILayout.Button("Sign out", GUILayout.Width(90))) SignOut();
                }
                GUILayout.EndHorizontal();
                EditorGUILayout.HelpBox("Play Mode signs in with this session automatically. It's stored on this machine only (EditorPrefs), never in your project.", MessageType.None);
            }
            else
            {
                email = EditorGUILayout.TextField("Email", email);
                password = EditorGUILayout.PasswordField("Password", password);
                GUILayout.BeginHorizontal();
                GUILayout.Space(EditorGUIUtility.labelWidth + 2);
                using (new EditorGUI.DisabledScope(busy || string.IsNullOrWhiteSpace(email) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(Slug)))
                    if (GUILayout.Button(busy ? "Signing in…" : "Sign in", GUILayout.Height(24))) _ = SignIn();
                GUILayout.EndHorizontal();
                EditorGUILayout.HelpBox("Use your Cool GameX account. Only a test session for this game is kept — your password is never stored.", MessageType.None);
            }
        }

        private void DrawCatalog()
        {
            Section("3   Achievements & DLC");
            GUILayout.BeginHorizontal();
            using (new EditorGUI.DisabledScope(busy || string.IsNullOrEmpty(Slug)))
            {
                if (GUILayout.Button("Load from server", GUILayout.Width(130))) _ = LoadCatalog();
                if (GUILayout.Button("Generate IDs", GUILayout.Width(110))) _ = GenerateIds();
            }
            GUILayout.EndHorizontal();

            if (catalog == null || catalogSlug != Slug)
            {
                GUILayout.Label("Load your achievements to test unlocks and generate type-safe IDs (Ids.Achievements.*).", Styles.Muted);
                return;
            }

            EditorGUILayout.Space(4);
            if (catalog.achievements.Length == 0) GUILayout.Label("No achievements yet — add them in the Creator dashboard.", Styles.Muted);
            foreach (var a in catalog.achievements)
            {
                GUILayout.BeginHorizontal(Styles.Row);
                GUILayout.BeginVertical();
                GUILayout.Label(a.name, EditorStyles.boldLabel);
                GUILayout.Label($"{a.slug}  ·  {a.points} pts", Styles.Muted);
                GUILayout.EndVertical();
                GUILayout.FlexibleSpace();
                using (new EditorGUI.DisabledScope(busy || !SignedIn))
                    if (GUILayout.Button(new GUIContent("Test unlock", SignedIn ? "Unlocks it on your account" : "Sign in first"), GUILayout.Width(96))) _ = TestUnlock(a.slug);
                GUILayout.EndHorizontal();
            }
            if (catalog.dlc.Length > 0)
            {
                EditorGUILayout.Space(4);
                GUILayout.Label("DLC", EditorStyles.miniBoldLabel);
                foreach (var d in catalog.dlc) GUILayout.Label($"{d.name}   ·   {d.slug}", Styles.Muted);
            }
        }

        private void DrawOptions()
        {
            EditorGUILayout.Space(10);
            showOptions = EditorGUILayout.Foldout(showOptions, "4   Options", true, Styles.Foldout);
            if (!showOptions) return;
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(so.FindProperty("autoInitialize"), new GUIContent("Auto-start", "Start the SDK when the game launches — no code or scene object needed."));
            EditorGUILayout.PropertyField(so.FindProperty("playtime"), new GUIContent("Playtime"));
            EditorGUILayout.PropertyField(so.FindProperty("heartbeatMinutes"), new GUIContent("Heartbeat (min)"));
            EditorGUILayout.PropertyField(so.FindProperty("offlineQueue"), new GUIContent("Offline queue"));
            EditorGUILayout.PropertyField(so.FindProperty("logLevel"), new GUIContent("Log level"));
            EditorGUILayout.PropertyField(so.FindProperty("debugOverlay"), new GUIContent("Debug overlay"));
            EditorGUILayout.PropertyField(so.FindProperty("debugOverlayKey"), new GUIContent("Overlay key"));
            EditorGUILayout.PropertyField(so.FindProperty("inGameToasts"), new GUIContent("In-game toasts"));
            EditorGUI.indentLevel--;
        }

        private void DrawFooter()
        {
            EditorGUILayout.Space(14);
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Documentation", EditorStyles.linkLabel)) Application.OpenURL(DocsUrl);
            GUILayout.Label("·", Styles.Muted);
            if (GUILayout.Button("Creator dashboard", EditorStyles.linkLabel)) Application.OpenURL(SiteUrl);
            GUILayout.Label("·", Styles.Muted);
            if (GUILayout.Button("Discord", EditorStyles.linkLabel)) Application.OpenURL(DiscordUrl);
            GUILayout.FlexibleSpace();
            GUILayout.EndHorizontal();
        }

        // ------------------------------------------------------------------
        //  Actions
        // ------------------------------------------------------------------

        private async Task TestConnection()
        {
            busy = true;
            var r = await EditorHttp.Get(Server + "/api/health");
            busy = false;
            connectionOk = r.ok;
            connectionText = r.ok ? $"✓  Connected to {Server}" : r.offline ? $"✕  Can't reach {Server}" : $"✕  {r.error}";
            Repaint();
        }

        private async Task SignIn()
        {
            busy = true;
            Repaint();
            var body = JsonUtility.ToJson(new LoginReq { email = email.Trim(), password = password, gameSlug = Slug });
            var r = await EditorHttp.Post(Server + "/api/sdk/editor-login", body);
            busy = false;
            password = "";
            if (!r.ok)
            {
                if ((r.error ?? "").StartsWith("Not found:")) Note("This server doesn't support Editor sign-in yet — deploy the latest Cool GameX server.", MessageType.Error);
                else Note(r.offline ? $"Can't reach {Server}." : r.error, MessageType.Error);
                Repaint();
                return;
            }
            var resp = JsonUtility.FromJson<LoginResp>(r.body);
            session = new EditorSession.Data
            {
                token = resp.token,
                expiresAt = resp.expiresAt,
                gameSlug = resp.gameSlug,
                userId = resp.user?.id,
                email = resp.user?.email,
                username = resp.user?.username,
            };
            EditorSession.Save(session);
            Note($"Signed in as {(string.IsNullOrEmpty(session.username) ? session.email : session.username)}. Press Play — the SDK uses this session automatically.", MessageType.Info);
            Repaint();
            await LoadCatalog();
        }

        private async Task MaybeRefreshSession()
        {
            var d = EditorSession.Load();
            if (!EditorSession.IsValid(d)) return;
            if (d.expiresAt - DateTimeOffset.UtcNow.ToUnixTimeSeconds() > 2 * 3600) return;
            await RefreshSession(false);
        }

        private async Task RefreshSession(bool announce)
        {
            var d = EditorSession.Load();
            if (d == null || string.IsNullOrEmpty(d.token)) return;
            busy = true;
            var r = await EditorHttp.Post(Server + "/api/sdk/refresh", "{}", d.token);
            busy = false;
            if (r.ok)
            {
                var rr = JsonUtility.FromJson<RefreshResp>(r.body);
                d.token = rr.token;
                d.expiresAt = rr.expiresAt;
                EditorSession.Save(d);
                session = d;
                if (announce) Note("Session refreshed.", MessageType.Info);
            }
            else if (r.status == 401)
            {
                EditorSession.Clear();
                session = null;
                Note("Your test session expired — sign in again.", MessageType.Warning);
            }
            else if (announce)
            {
                Note(r.offline ? $"Can't reach {Server}." : r.error, MessageType.Warning);
            }
            Repaint();
        }

        private void SignOut()
        {
            EditorSession.Clear();
            session = null;
            Note("Signed out.", MessageType.Info);
        }

        private async Task LoadCatalog()
        {
            if (string.IsNullOrEmpty(Slug)) { Note("Enter your game slug first.", MessageType.Warning); return; }
            busy = true;
            Repaint();
            string escaped = Uri.EscapeDataString(Slug);
            EditorHttp.Response r;
            if (SignedIn)
            {
                r = await EditorHttp.Get($"{Server}/api/sdk/catalog?gameSlug={escaped}", session.token);
                if (!r.ok && (r.error ?? "").StartsWith("Not found:")) r = await EditorHttp.Get($"{Server}/api/games/{escaped}");
            }
            else
            {
                r = await EditorHttp.Get($"{Server}/api/games/{escaped}");
            }
            busy = false;

            if (!r.ok)
            {
                Note(r.status == 404
                    ? $"No game \"{Slug}\" found.{(SignedIn ? "" : " If it's still a draft, sign in first.")}"
                    : r.offline ? $"Can't reach {Server}." : r.error, MessageType.Warning);
                Repaint();
                return;
            }

            catalog = JsonUtility.FromJson<Catalog>(r.body) ?? new Catalog();
            if (catalog.achievements == null) catalog.achievements = Array.Empty<CatalogItem>();
            if (catalog.dlc == null) catalog.dlc = Array.Empty<CatalogItem>();
            catalogSlug = Slug;
            Note($"Loaded {catalog.achievements.Length} achievement(s) and {catalog.dlc.Length} DLC for \"{Slug}\".", MessageType.Info);
            Repaint();
        }

        internal async Task GenerateIds()
        {
            if (catalog == null || catalogSlug != Slug) await LoadCatalog();
            if (catalog == null || catalogSlug != Slug) return;
            string code = IdGenerator.Generate(Slug, ToItems(catalog.achievements), ToItems(catalog.dlc));
            IdGenerator.Write(code);
            Note($"Wrote {IdGenerator.DefaultPath}. Use Ids.Achievements.* and Ids.DLC.* in your scripts.", MessageType.Info);
            Repaint();
        }

        private async Task TestUnlock(string slug)
        {
            busy = true;
            Repaint();
            var body = JsonUtility.ToJson(new UnlockReq { gameSlug = Slug, achievementSlug = slug });
            var r = await EditorHttp.Post($"{Server}/api/achievements/unlock", body, session.token);
            busy = false;
            if (r.ok)
            {
                bool newly = JsonUtility.FromJson<UnlockResp>(r.body)?.newlyUnlocked ?? false;
                Note(newly ? $"Unlocked \"{slug}\" on your account." : $"\"{slug}\" was already unlocked on your account.", MessageType.Info);
            }
            else Note($"Unlock failed: {r.error}", MessageType.Error);
            Repaint();
        }

        // ------------------------------------------------------------------
        //  Helpers
        // ------------------------------------------------------------------

        private void Note(string text, MessageType type)
        {
            note = text;
            noteType = type;
        }

        private static List<IdGenerator.Item> ToItems(CatalogItem[] items)
        {
            var list = new List<IdGenerator.Item>();
            foreach (var i in items) list.Add(new IdGenerator.Item { slug = i.slug, name = i.name });
            return list;
        }

        private static string Expiry(long exp)
        {
            var left = DateTimeOffset.FromUnixTimeSeconds(exp) - DateTimeOffset.UtcNow;
            if (left.TotalSeconds <= 0) return "Expired";
            return $"Valid for {(int)left.TotalHours}h {left.Minutes}m (refreshes automatically)";
        }

        private static void Check(string label, bool ok)
        {
            GUILayout.Label((ok ? "●  " : "○  ") + label, ok ? Styles.Good : Styles.Muted, GUILayout.ExpandWidth(false));
            GUILayout.Space(10);
        }

        private static void Section(string title)
        {
            EditorGUILayout.Space(12);
            GUILayout.Label(title, Styles.Section);
            var r = GUILayoutUtility.GetRect(1f, 1f, GUILayout.ExpandWidth(true));
            EditorGUI.DrawRect(new Rect(r.x, r.y, r.width, 1f), new Color(1f, 1f, 1f, 0.08f));
            EditorGUILayout.Space(6);
        }

        internal static CoolGameXSettings FindSettings()
        {
            foreach (string guid in AssetDatabase.FindAssets("t:CoolGameXSettings"))
            {
                var s = AssetDatabase.LoadAssetAtPath<CoolGameXSettings>(AssetDatabase.GUIDToAssetPath(guid));
                if (s != null) return s;
            }
            return null;
        }

        private static CoolGameXSettings CreateSettings()
        {
            if (!AssetDatabase.IsValidFolder(SettingsFolder)) AssetDatabase.CreateFolder("Assets", "Resources");
            var s = CreateInstance<CoolGameXSettings>();
            AssetDatabase.CreateAsset(s, SettingsPath);
            AssetDatabase.SaveAssets();
            return s;
        }

        private static class Styles
        {
            internal static GUIStyle Page, Title, Muted, Good, Bad, Section, Row, Foldout;

            internal static void Ensure()
            {
                if (Page != null) return;
                Page = new GUIStyle { padding = new RectOffset(16, 16, 14, 16) };
                Title = new GUIStyle(EditorStyles.boldLabel) { fontSize = 20, fixedHeight = 28 };
                Muted = new GUIStyle(EditorStyles.wordWrappedMiniLabel) { normal = { textColor = new Color(0.6f, 0.6f, 0.65f) } };
                Good = new GUIStyle(EditorStyles.miniBoldLabel) { normal = { textColor = new Color(0.35f, 0.8f, 0.5f) } };
                Bad = new GUIStyle(EditorStyles.miniBoldLabel) { wordWrap = true, normal = { textColor = new Color(0.95f, 0.45f, 0.45f) } };
                Section = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13 };
                Row = new GUIStyle(EditorStyles.helpBox) { padding = new RectOffset(8, 8, 6, 6), margin = new RectOffset(0, 0, 2, 2) };
                Foldout = new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold, fontSize = 13 };
            }
        }
    }

    /// <summary>Adds an "Open Setup" button to the settings asset's inspector.</summary>
    [CustomEditor(typeof(CoolGameXSettings))]
    internal class CoolGameXSettingsInspector : UnityEditor.Editor
    {
        public override void OnInspectorGUI()
        {
            if (GUILayout.Button("Open Cool GameX Setup", GUILayout.Height(28))) CoolGameXSetupWindow.Open();
            EditorGUILayout.Space(6);
            DrawDefaultInspector();
        }
    }

    /// <summary>Opens the Setup window once, the first time the SDK is imported into a project.</summary>
    [InitializeOnLoad]
    internal static class CoolGameXWelcome
    {
        static CoolGameXWelcome()
        {
            if (Application.isBatchMode) return;
            EditorApplication.delayCall += () =>
            {
                string key = "CoolGameX.WelcomeShown::" + Application.dataPath;
                if (EditorPrefs.GetBool(key, false)) return;
                EditorPrefs.SetBool(key, true);
                if (CoolGameXSetupWindow.FindSettings() == null) CoolGameXSetupWindow.Open();
            };
        }
    }
}
