// 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.Globalization;
using UnityEngine;

namespace EzAdmin.Client
{

    public static class EzAdminGame
    {
        private const string CacheKey = "ezadmin.liveops";

        private static readonly Dictionary<string, Entry> Settings = new Dictionary<string, Entry>();
        private static readonly List<EzAdminNewsPost> Posts = new List<EzAdminNewsPost>();
        private static bool _loaded;
        private static bool _fetching;

        public static event Action Changed;

        public static bool Ready { get; private set; }

        public static long Revision { get; private set; } = -1;

        public static IReadOnlyList<EzAdminNewsPost> News
        {
            get { LoadCache(); return Posts; }
        }

        public static bool Has(string key)
        {
            LoadCache();
            return key != null && Settings.ContainsKey(key);
        }

        public static string GetString(string key, string fallback = "")
        {
            LoadCache();
            if (key == null || !Settings.TryGetValue(key, out var entry)) return fallback;
            switch (entry.type)
            {
                case "text": return entry.text ?? fallback;
                case "number": return entry.number.ToString(CultureInfo.InvariantCulture);
                default: return entry.on ? "on" : "off";
            }
        }

        public static float GetFloat(string key, float fallback = 0f)
        {
            LoadCache();
            return key != null && Settings.TryGetValue(key, out var entry) && entry.type == "number" ? (float)entry.number : fallback;
        }

        public static int GetInt(string key, int fallback = 0)
        {
            LoadCache();
            if (key == null || !Settings.TryGetValue(key, out var entry) || entry.type != "number") return fallback;
            double value = Math.Round(entry.number);
            return value > int.MaxValue ? int.MaxValue : value < int.MinValue ? int.MinValue : (int)value;
        }

        public static bool GetBool(string key, bool fallback = false)
        {
            LoadCache();
            return key != null && Settings.TryGetValue(key, out var entry) && entry.type == "switch" ? entry.on : fallback;
        }

        public static void Refresh()
        {
            Fetch();
        }

        internal static void Offer(long revision)
        {
            LoadCache();
            if (revision == Revision && Ready) return;
            if (revision <= 0)
            {

                if (Revision != 0) Apply(new Reply { ok = true, rev = 0, config = new Entry[0], news = new Post[0] }, true);
                return;
            }
            Fetch();
        }

        private static void Fetch()
        {
            var client = EzAdminClient.Instance;
            if (client == null || _fetching) return;
            _fetching = true;
            client.FetchLiveOps(text =>
            {
                _fetching = false;
                Reply reply = null;
                try { reply = string.IsNullOrEmpty(text) ? null : JsonUtility.FromJson<Reply>(text); }
                catch (ArgumentException) { reply = null; }
                if (reply != null && reply.ok) Apply(reply, true);
            });
        }

        private static void Apply(Reply reply, bool save)
        {
            Settings.Clear();
            foreach (var entry in reply.config ?? new Entry[0])
            {
                if (entry != null && !string.IsNullOrEmpty(entry.key)) Settings[entry.key] = entry;
            }
            Posts.Clear();
            foreach (var post in reply.news ?? new Post[0])
            {
                if (post != null) Posts.Add(new EzAdminNewsPost(post.id, post.title, post.body, post.posted));
            }
            Revision = reply.rev;
            Ready = true;
            if (save)
            {
                try
                {
                    PlayerPrefs.SetString(CacheKey, JsonUtility.ToJson(reply));
                    PlayerPrefs.Save();
                }
                catch (Exception) {  }
            }
            try { Changed?.Invoke(); }
            catch (Exception exc) { Debug.LogException(exc); }
        }

        private static void LoadCache()
        {
            if (_loaded) return;
            _loaded = true;
            try
            {
                string text = PlayerPrefs.GetString(CacheKey, "");
                if (string.IsNullOrEmpty(text)) return;
                var reply = JsonUtility.FromJson<Reply>(text);
                if (reply != null && reply.ok)
                {
                    Apply(reply, false);
                    Ready = true;
                    Revision = -1;
                }
            }
            catch (Exception) {  }
        }

        [Serializable]
        [UnityEngine.Scripting.Preserve]
        internal class Entry
        {
            [UnityEngine.Scripting.Preserve] public string key;
            [UnityEngine.Scripting.Preserve] public string type;
            [UnityEngine.Scripting.Preserve] public string text;
            [UnityEngine.Scripting.Preserve] public double number;
            [UnityEngine.Scripting.Preserve] public bool on;
        }

        [Serializable]
        [UnityEngine.Scripting.Preserve]
        internal class Post
        {
            [UnityEngine.Scripting.Preserve] public string id;
            [UnityEngine.Scripting.Preserve] public string title;
            [UnityEngine.Scripting.Preserve] public string body;
            [UnityEngine.Scripting.Preserve] public long posted;
        }

        [Serializable]
        [UnityEngine.Scripting.Preserve]
        internal class Reply
        {
            [UnityEngine.Scripting.Preserve] public bool ok;
            [UnityEngine.Scripting.Preserve] public long rev;
            [UnityEngine.Scripting.Preserve] public Entry[] config;
            [UnityEngine.Scripting.Preserve] public Post[] news;
        }
    }

    public sealed class EzAdminNewsPost
    {
        public string Id { get; }
        public string Title { get; }
        public string Body { get; }

        public DateTime Posted { get; }

        internal EzAdminNewsPost(string id, string title, string body, long posted)
        {
            Id = id ?? "";
            Title = title ?? "";
            Body = body ?? "";
            Posted = DateTimeOffset.FromUnixTimeSeconds(Math.Max(0, posted)).UtcDateTime;
        }
    }
}
