// Copyright (c) 2026 EzAdmin. All rights reserved.
// Licensed under the EzAdmin Client License. See LICENSE-CLIENT.txt.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using EzAdmin.Client;
using UnityEditor;
using UnityEngine;

namespace EzAdmin.EditorTools
{

    [InitializeOnLoad]
    public static class EzAdminUpdater
    {
        private const string Root = "Assets/EzAdmin/";
        private const string Protected = "Assets/EzAdmin/MyGame/";
        private static readonly double CheckEveryHours = 6;

        private static string Key(string name) => "EzAdmin.Updater." + name + "." + Application.productName;

        private const string FilesKeyPath = "Assets/EzAdmin/Editor/EzAdminFilesKey.txt";

        private static HttpClient NewClient(int seconds)
        {
            var http = new HttpClient { Timeout = TimeSpan.FromSeconds(seconds) };
            try
            {
                if (File.Exists(FilesKeyPath))
                {
                    string key = File.ReadAllText(FilesKeyPath).Trim();
                    if (key.StartsWith("ezf_", StringComparison.Ordinal) && key.Length < 120)
                        http.DefaultRequestHeaders.Add("X-EzAdmin-Files-Key", key);
                }
            }
            catch (Exception) { }
            return http;
        }

        static EzAdminUpdater()
        {
            EditorApplication.delayCall += () =>
            {
                double last;
                double.TryParse(EditorPrefs.GetString(Key("LastCheck"), "0"), out last);
                double now = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalHours;
                if (now - last < CheckEveryHours) return;
                EditorPrefs.SetString(Key("LastCheck"), now.ToString(System.Globalization.CultureInfo.InvariantCulture));
                _ = Check(false);
            };
        }

        [MenuItem("Tools/EzAdmin/Check for Updates", false, 1)]
        [MenuItem("Window/EzAdmin/Check for Updates", false, 1)]
        private static void CheckNow()
        {
            _ = Check(true);
        }

        [MenuItem("Tools/EzAdmin/Turn Automatic Updates Off", false, 2)]
        private static void AutoOff()
        {
            EditorPrefs.SetBool(Key("Auto"), false);
            Debug.Log("EzAdmin: automatic updates are off. You will be asked before each update.");
        }

        [Serializable]
        private class Manifest
        {
            public bool ok;
            public string version;
            public string[] notes;
            public RemoteFile[] files;
        }

        [Serializable]
        private class RemoteFile
        {
            public string path;
            public string sha256;
            public string meta;
        }

        public static string Address()
        {
            return EzAdminClient.SiteAddress;
        }

        public static void RememberAddress(string address)
        {
            if (!string.IsNullOrWhiteSpace(address)) EditorPrefs.SetString(Key("Address"), address.Trim().TrimEnd('/'));
        }

        private static async Task Check(bool userAsked)
        {
            string address = Address();
            if (string.IsNullOrEmpty(address))
            {
                if (userAsked)
                {
                    EditorUtility.DisplayDialog("EzAdmin", "Set your EzAdmin address first: Window › EzAdmin › Setup Wizard.", "OK");
                }
                return;
            }

            Manifest manifest;
            try
            {
                using (var http = NewClient(20))
                {
                    string json = await http.GetStringAsync(address + "/unity/v1/files/manifest?since=" +
                                                            Uri.EscapeDataString(EzAdminClient.Version));
                    manifest = JsonUtility.FromJson<Manifest>(json);
                }
            }
            catch (Exception e)
            {
                if (userAsked) EditorUtility.DisplayDialog("EzAdmin", "Could not reach EzAdmin at " + address + ".\n\n" + e.Message, "OK");
                return;
            }
            if (manifest == null || !manifest.ok || manifest.files == null)
            {
                if (userAsked) EditorUtility.DisplayDialog("EzAdmin", "EzAdmin did not answer with a file list.", "OK");
                return;
            }

            var changed = new List<RemoteFile>();
            foreach (var file in manifest.files)
            {
                if (!Allowed(file.path)) continue;
                if (!File.Exists(file.path) || Sha256(File.ReadAllBytes(file.path)) != file.sha256)
                {
                    changed.Add(file);
                }
            }
            if (changed.Count == 0)
            {
                if (userAsked) EditorUtility.DisplayDialog("EzAdmin", "EzAdmin is up to date (version " + manifest.version + ").", "OK");
                return;
            }

            if (!EditorPrefs.GetBool(Key("Auto"), false))
            {
                var message = new StringBuilder();
                message.AppendLine("EzAdmin " + manifest.version + " for Unity is ready. " + changed.Count +
                                   " file" + (changed.Count == 1 ? "" : "s") + " will be updated.");
                if (manifest.notes != null && manifest.notes.Length > 0)
                {
                    message.AppendLine();
                    for (int i = 0; i < manifest.notes.Length && i < 6; i++) message.AppendLine("• " + manifest.notes[i]);
                }
                message.AppendLine();
                message.AppendLine("Changes you made inside EzAdmin's own files are replaced. Assets/EzAdmin/MyGame is never touched.");
                int choice = EzAdminDialog.Ask("Update EzAdmin", message.ToString(),
                                               "Update now", "Later", "Always update automatically");
                if (choice == 1) return;
                if (choice == 2) EditorPrefs.SetBool(Key("Auto"), true);
            }

            await Apply(address, changed, manifest.version);
        }

        private static async Task Apply(string address, List<RemoteFile> files, string version)
        {
            var downloaded = new Dictionary<RemoteFile, byte[]>();
            try
            {
                using (var http = NewClient(30))
                {
                    for (int i = 0; i < files.Count; i++)
                    {
                        EditorUtility.DisplayProgressBar("Updating EzAdmin", files[i].path, (float)i / files.Count);
                        byte[] bytes = await http.GetByteArrayAsync(address + "/unity/v1/files/raw?path=" +
                                                                    Uri.EscapeDataString(files[i].path));
                        if (Sha256(bytes) != files[i].sha256)
                        {
                            throw new InvalidDataException(files[i].path + " did not arrive intact.");
                        }
                        downloaded[files[i]] = bytes;
                    }
                }
            }
            catch (Exception e)
            {
                EditorUtility.ClearProgressBar();
                EditorUtility.DisplayDialog("EzAdmin", "The update did not download, so nothing was changed.\n\n" + e.Message, "OK");
                return;
            }

            foreach (var pair in downloaded)
            {
                string path = pair.Key.path;
                Directory.CreateDirectory(Path.GetDirectoryName(path));
                File.WriteAllBytes(path, pair.Value);
                if (!File.Exists(path + ".meta") && !string.IsNullOrEmpty(pair.Key.meta))
                {
                    File.WriteAllText(path + ".meta", pair.Key.meta);
                }
            }
            EditorUtility.ClearProgressBar();
            Debug.Log("EzAdmin: updated to " + version + " (" + downloaded.Count + " file" +
                      (downloaded.Count == 1 ? "" : "s") + ").");
            AssetDatabase.Refresh();
        }

        private static bool Allowed(string path)
        {
            return !string.IsNullOrEmpty(path)
                   && path.StartsWith(Root, StringComparison.Ordinal)
                   && !path.StartsWith(Protected, StringComparison.Ordinal)
                   && !path.Contains("..")
                   && !Path.IsPathRooted(path);
        }

        private static string Sha256(byte[] bytes)
        {
            using (var sha = SHA256.Create())
            {
                var hash = sha.ComputeHash(bytes);
                var text = new StringBuilder(hash.Length * 2);
                foreach (byte b in hash) text.Append(b.ToString("x2"));
                return text.ToString();
            }
        }
    }

    [InitializeOnLoad]
    internal static class EzAdminCleanup
    {
        private static readonly string[] Retired =
        {
            "EzAdminItemGate", "EzAdminItemPicker", "EzAdminSignInObjects", "EzAdminVRGuard",
        };

        static EzAdminCleanup()
        {
            EditorApplication.delayCall += Run;
        }

        private static void Run()
        {
            var removed = new List<string>();
            foreach (string name in Retired)
            {
                foreach (string guid in AssetDatabase.FindAssets(name + " t:MonoScript"))
                {
                    string path = AssetDatabase.GUIDToAssetPath(guid);
                    if (Path.GetFileNameWithoutExtension(path) != name) continue;
                    if (!File.ReadAllText(path).Contains("Copyright (c) 2026 EzAdmin.")) continue;
                    if (AssetDatabase.DeleteAsset(path)) removed.Add(path);
                }
            }
            if (removed.Count > 0)
                Debug.Log("EzAdmin: removed old scripts it no longer uses: " + string.Join(", ", removed));
        }
    }

    internal sealed class EzAdminDialog : EditorWindow
    {
        private string _message;
        private string _ok, _cancel, _alt;
        private int _choice = 1;
        private Vector2 _scroll;

        public static int Ask(string title, string message, string ok, string cancel, string alt)
        {
            var window = CreateInstance<EzAdminDialog>();
            window.titleContent = new GUIContent(title);
            window._message = message;
            window._ok = ok;
            window._cancel = cancel;
            window._alt = alt;
            var size = new Vector2(460, 230);
            window.minSize = window.maxSize = size;
            var main = EditorGUIUtility.GetMainWindowPosition();
            window.position = new Rect(main.center - size / 2, size);
            window.ShowModalUtility();
            return window._choice;
        }

        private void OnGUI()
        {
            EditorGUI.DrawRect(new Rect(0, 0, position.width, position.height), EzAdminBrand.Panel);
            GUI.DrawTexture(new Rect(18, 18, 56, 56), EzAdminBrand.Logo(128), ScaleMode.ScaleToFit);
            var text = new GUIStyle(EditorStyles.wordWrappedLabel) { fontSize = 12 };
            text.normal.textColor = new Color(0.9f, 0.92f, 0.95f);
            GUILayout.BeginArea(new Rect(90, 16, position.width - 108, position.height - 70));
            _scroll = GUILayout.BeginScrollView(_scroll);
            GUILayout.Label(_message, text);
            GUILayout.EndScrollView();
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(12, position.height - 44, position.width - 24, 32));
            GUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            if (GUILayout.Button(_ok, GUILayout.Height(26), GUILayout.MinWidth(90))) Pick(0);
            if (!string.IsNullOrEmpty(_alt) && GUILayout.Button(_alt, GUILayout.Height(26))) Pick(2);
            if (GUILayout.Button(_cancel, GUILayout.Height(26), GUILayout.MinWidth(70))) Pick(1);
            GUILayout.EndHorizontal();
            GUILayout.EndArea();
        }

        private void Pick(int choice)
        {
            _choice = choice;
            Close();
        }
    }
}
