using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Random = UnityEngine.Random;

namespace MajacloudSdk.Samples.Starter
{
    /// <summary>
    /// Playground: try every call with your own values and add test data.
    /// Each result goes to the log (and a toast). "Run all tests" checks every service once.
    /// </summary>
    public sealed class TestDataPanel : MonoBehaviour
    {
        private const string Green = "#16A34A";
        private const string Red = "#D93F44";
        private const string Grey = "#6A7890";

        [Header("Quick test")]
        [SerializeField] private Button runAllButton;

        [Header("Leaderboard")]
        [SerializeField] private TMP_InputField slugInput;
        [SerializeField] private TMP_InputField scoreInput;
        [SerializeField] private Button submitButton;
        [SerializeField] private Button randomScoreButton;
        [SerializeField] private Button testPlayersButton;
        [SerializeField] private int testPlayerCount = 5;

        [Header("Cloud save")]
        [SerializeField] private TMP_InputField saveKeyInput;
        [SerializeField] private TMP_InputField saveJsonInput;
        [SerializeField] private Button saveButton;
        [SerializeField] private Button loadButton;
        [SerializeField] private Button listSavesButton;
        [SerializeField] private Button deleteSaveButton;

        [Header("Rewards")]
        [SerializeField] private TMP_InputField rewardKeyInput;
        [SerializeField] private Button claimButton;
        [SerializeField] private Button walletButton;
        [SerializeField] private Button listRewardsButton;

        [Header("Player")]
        [SerializeField] private TMP_InputField nameInput;
        [SerializeField] private Button setNameButton;
        [SerializeField] private Button newGuestButton;

        [Header("Remote config")]
        [SerializeField] private TMP_InputField configKeyInput;
        [SerializeField] private Button getConfigButton;
        [SerializeField] private Button fetchConfigButton;

        [Header("Log")]
        [SerializeField] private TMP_Text logLabel;
        [SerializeField] private Button clearLogButton;
        [SerializeField] private int maxLogLines = 40;

        private readonly List<string> _lines = new List<string>();
        private bool _busy;

        private string Slug => Read(slugInput, "coins");
        private string SaveKey => Read(saveKeyInput, "test_data");
        private string RewardKey => Read(rewardKeyInput, "daily_coins");

        private void Awake()
        {
            Bind(runAllButton, RunAll);
            Bind(submitButton, SubmitScore);
            Bind(randomScoreButton, SubmitRandomScore);
            Bind(testPlayersButton, AddTestPlayers);
            Bind(saveButton, Save);
            Bind(loadButton, Load);
            Bind(listSavesButton, ListSaves);
            Bind(deleteSaveButton, DeleteSave);
            Bind(claimButton, Claim);
            Bind(walletButton, ShowWallet);
            Bind(listRewardsButton, ListRewards);
            Bind(setNameButton, SetName);
            Bind(newGuestButton, NewGuest);
            Bind(getConfigButton, GetConfig);
            Bind(fetchConfigButton, FetchConfig);
            Bind(clearLogButton, ClearLog);
            Info("Ready. Press any button, or Run all tests.");
        }

        // --- Leaderboard ---------------------------------------------------------------------

        public async void SubmitScore()
        {
            if (!double.TryParse(Read(scoreInput, string.Empty), NumberStyles.Float, CultureInfo.InvariantCulture, out var score))
            {
                Fail("Enter a score first.");
                return;
            }
            await Submit(score);
        }

        public async void SubmitRandomScore()
        {
            int score = Random.Range(1, 1000);
            if (scoreInput != null) scoreInput.text = score.ToString(CultureInfo.InvariantCulture);
            await Submit(score);
        }

        private async Task Submit(double score)
        {
            if (!Ready()) return;
            var slug = Slug;
            var result = await Majacloud.Leaderboards.SubmitScoreAsync(slug, score);
            if (this == null) return;
            if (!result.Ok)
            {
                Fail("Submit to " + slug + ": " + Explain(result.Error));
                return;
            }
            var entry = result.Value.Entry;
            Pass(result.Value.Accepted
                ? "Score " + Number(score) + " saved to " + slug + (entry != null && entry.Rank.HasValue ? ", rank #" + entry.Rank.Value : string.Empty)
                : "Score " + Number(score) + " sent; your best on " + slug + " is still " + (entry != null ? Number(entry.Score) : "higher"));
        }

        public void AddTestPlayers()
        {
            if (!Ready() || _busy) return;
            StartCoroutine(AddTestPlayersRoutine(Slug));
        }

        private IEnumerator AddTestPlayersRoutine(string slug)
        {
            _busy = true;
            SetInteractable(testPlayersButton, false);
            Info("Adding " + testPlayerCount + " test players to " + slug + "…");
            int added = 0;
            yield return TestPlayers.Create(slug, testPlayerCount, (ok, message) =>
            {
                if (ok) added++;
                Write(ok, message);
            });
            _busy = false;
            SetInteractable(testPlayersButton, true);
            StarterController.Toast(added + " test player" + (added == 1 ? string.Empty : "s") + " added", added == 0);
        }

        // --- Cloud save ----------------------------------------------------------------------

        public async void Save()
        {
            if (!Ready()) return;
            var key = SaveKey;
            var json = Read(saveJsonInput, "{}");
            var result = await Majacloud.CloudSave.SaveJsonAsync(key, json);
            if (this == null) return;
            if (!result.Ok) Fail("Save \"" + key + "\": " + Explain(result.Error));
            else Pass("Saved \"" + key + "\" (revision " + result.Value.Revision + ", " + result.Value.SizeBytes + " bytes)");
        }

        public async void Load()
        {
            if (!Ready()) return;
            var key = SaveKey;
            var result = await Majacloud.CloudSave.LoadJsonAsync(key);
            if (this == null) return;
            if (result.IsNotFound)
            {
                Info("No save called \"" + key + "\" yet. Press Save first.");
                return;
            }
            if (!result.Ok)
            {
                Fail("Load \"" + key + "\": " + Explain(result.Error));
                return;
            }
            if (saveJsonInput != null) saveJsonInput.text = result.Value.Data;
            Pass("Loaded \"" + key + "\" (revision " + result.Value.Revision + "): " + Short(result.Value.Data));
        }

        public async void ListSaves()
        {
            if (!Ready()) return;
            var result = await Majacloud.CloudSave.ListAsync();
            if (this == null) return;
            if (!result.Ok)
            {
                Fail("List saves: " + Explain(result.Error));
                return;
            }
            if (result.Value.Count == 0)
            {
                Info("This player has no saves yet.");
                return;
            }
            var keys = new StringBuilder();
            foreach (var entry in result.Value)
            {
                if (keys.Length > 0) keys.Append(", ");
                keys.Append(entry.Key).Append(" (rev ").Append(entry.Revision).Append(')');
            }
            Pass(result.Value.Count + " save" + (result.Value.Count == 1 ? string.Empty : "s") + ": " + keys);
        }

        public async void DeleteSave()
        {
            if (!Ready()) return;
            var key = SaveKey;
            var result = await Majacloud.CloudSave.DeleteAsync(key);
            if (this == null) return;
            if (!result.Ok) Fail("Delete \"" + key + "\": " + Explain(result.Error));
            else if (result.Value) Pass("Deleted \"" + key + "\"");
            else Info("There was no save called \"" + key + "\".");
        }

        // --- Rewards -------------------------------------------------------------------------

        public async void Claim()
        {
            if (!Ready()) return;
            var key = RewardKey;
            var result = await Majacloud.Rewards.ClaimAsync(key);
            if (this == null) return;
            if (!result.Ok)
            {
                if (result.Error.Code == MajacloudErrorCode.Conflict)
                {
                    var next = result.Error.GetDetailDateTime("next_claim_at");
                    Info("\"" + key + "\" was already claimed" + (next.HasValue ? ". Next claim at " + next.Value.ToLocalTime().ToString("MMM d HH:mm", CultureInfo.InvariantCulture) : string.Empty) + ".");
                    StarterController.Toast("Already claimed", true);
                    return;
                }
                Fail("Claim \"" + key + "\": " + Explain(result.Error));
                return;
            }
            var claim = result.Value;
            Pass("+" + Number(claim.Amount) + " " + claim.Currency + " from \"" + key + "\", balance " + Number(claim.Balance));
        }

        public async void ShowWallet()
        {
            if (!Ready()) return;
            var result = await Majacloud.Rewards.GetWalletAsync();
            if (this == null) return;
            if (!result.Ok)
            {
                Fail("Wallet: " + Explain(result.Error));
                return;
            }
            var text = new StringBuilder();
            if (result.Value.Balances != null)
            {
                foreach (var pair in result.Value.Balances)
                {
                    if (text.Length > 0) text.Append(", ");
                    text.Append(Number(pair.Value)).Append(' ').Append(pair.Key);
                }
            }
            Pass("Wallet: " + (text.Length > 0 ? text.ToString() : "empty"));
        }

        public async void ListRewards()
        {
            if (!Ready()) return;
            var result = await Majacloud.Rewards.ListAsync();
            if (this == null) return;
            if (!result.Ok)
            {
                Fail("Rewards: " + Explain(result.Error));
                return;
            }
            if (result.Value.Count == 0)
            {
                Info("No active rewards. Create one in the dashboard.");
                return;
            }
            var text = new StringBuilder();
            foreach (var reward in result.Value)
            {
                if (text.Length > 0) text.Append(", ");
                text.Append(reward.RewardKey).Append(reward.Claimable == false ? " (waiting)" : " (ready)");
            }
            Pass(result.Value.Count + " reward" + (result.Value.Count == 1 ? string.Empty : "s") + ": " + text);
        }

        // --- Player --------------------------------------------------------------------------

        public async void SetName()
        {
            if (!Ready()) return;
            var result = await Majacloud.Auth.SetDisplayNameAsync(Read(nameInput, string.Empty));
            if (this == null) return;
            if (!result.Ok) Fail("Set name: " + Explain(result.Error));
            else Pass("Name is now " + result.Value.DisplayName);
        }

        public async void NewGuest()
        {
            if (!Ready() || _busy) return;
            _busy = true;
            await Majacloud.Auth.SignOutAsync();
            var result = await Majacloud.Auth.SignInAnonymouslyAsync();
            if (this == null) return;
            _busy = false;
            if (!result.Ok) Fail("New guest: " + Explain(result.Error));
            else Pass("Signed in as a new guest: " + result.Value.DisplayName);
        }

        // --- Remote config -------------------------------------------------------------------

        public void GetConfig()
        {
            if (!Ready()) return;
            var key = Read(configKeyInput, string.Empty);
            if (!Majacloud.RemoteConfig.IsLoaded)
            {
                Info("Config is not loaded yet. Press Fetch all.");
                return;
            }
            if (Majacloud.RemoteConfig.TryGet(key, out var value)) Pass(key + " = " + Short(value.Raw) + " (" + value.Type.ToString().ToLowerInvariant() + ")");
            else Fail("No config key \"" + key + "\". Keys: " + string.Join(", ", Majacloud.RemoteConfig.Keys));
        }

        public async void FetchConfig()
        {
            if (!Ready()) return;
            var result = await Majacloud.RemoteConfig.FetchAsync(true);
            if (this == null) return;
            if (!result.Ok) Fail("Fetch config: " + Explain(result.Error));
            else Pass("Config v" + result.Value.Version + ", " + result.Value.Count + " keys, " + (result.Value.SignatureVerified ? "signature verified" : "unsigned"));
        }

        // --- Run all -------------------------------------------------------------------------

        public async void RunAll()
        {
            if (!Ready() || _busy) return;
            _busy = true;
            SetInteractable(runAllButton, false);
            Info("Running all tests…");
            var slug = Slug;
            var stamp = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
            int passed = 0, total = 0;

            async Task Step(string name, Func<Task<(bool ok, string detail)>> run)
            {
                if (this == null) return;
                total++;
                var watch = System.Diagnostics.Stopwatch.StartNew();
                var (ok, detail) = await run();
                if (this == null) return;
                if (ok) passed++;
                Write(ok, name + " · " + watch.ElapsedMilliseconds + " ms · " + detail);
            }

            await Step("Connection", async () =>
            {
                var r = await Majacloud.Project.GetAsync();
                return r.Ok ? (true, r.Value.Name) : (false, Explain(r.Error));
            });
            await Step("Sign in", async () =>
            {
                var r = await Majacloud.Auth.SignInAnonymouslyAsync();
                return r.Ok ? (true, r.Value.DisplayName) : (false, Explain(r.Error));
            });
            await Step("Submit score", async () =>
            {
                var r = await Majacloud.Leaderboards.SubmitScoreAsync(slug, Random.Range(1, 1000));
                return r.Ok ? (true, r.Value.Accepted ? "new best on " + slug : "sent to " + slug) : (false, Explain(r.Error));
            });
            await Step("Read leaderboard", async () =>
            {
                var r = await Majacloud.Leaderboards.GetTopAsync(slug, 5);
                return r.Ok ? (true, r.Value.Total + " players on " + slug) : (false, Explain(r.Error));
            });
            await Step("Cloud save", async () =>
            {
                var r = await Majacloud.CloudSave.SaveJsonAsync("majacloud_test", "{\"stamp\":\"" + stamp + "\"}");
                return r.Ok ? (true, "revision " + r.Value.Revision) : (false, Explain(r.Error));
            });
            await Step("Cloud load", async () =>
            {
                var r = await Majacloud.CloudSave.LoadJsonAsync("majacloud_test");
                if (!r.Ok) return (false, Explain(r.Error));
                return r.Value.Data != null && r.Value.Data.Contains(stamp) ? (true, "data matches") : (false, "data does not match");
            });
            await Step("Wallet", async () =>
            {
                var r = await Majacloud.Rewards.GetWalletAsync();
                return r.Ok ? (true, Number(r.Value.Coins) + " coins") : (false, Explain(r.Error));
            });
            await Step("Rewards", async () =>
            {
                var r = await Majacloud.Rewards.ListAsync();
                return r.Ok ? (true, r.Value.Count + " active") : (false, Explain(r.Error));
            });
            await Step("Remote config", async () =>
            {
                var r = await Majacloud.RemoteConfig.FetchAsync(true);
                return r.Ok ? (true, "v" + r.Value.Version + ", " + r.Value.Count + " keys") : (false, Explain(r.Error));
            });

            if (this == null) return;
            _busy = false;
            SetInteractable(runAllButton, true);
            bool allPassed = passed == total;
            Write(allPassed, "Done: " + passed + " of " + total + " passed");
            StarterController.Toast(allPassed ? "All " + total + " tests passed" : (total - passed) + " test(s) failed, see the log", !allPassed);
        }

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

        public void ClearLog()
        {
            _lines.Clear();
            if (logLabel != null) logLabel.text = string.Empty;
        }

        private static bool Ready()
        {
            if (Majacloud.IsInitialized) return true;
            StarterController.Toast("Run Tools › Majacloud › Setup first.", true);
            return false;
        }

        private void Pass(string message)
        {
            Write(true, message);
            StarterController.Toast(message);
        }

        private void Fail(string message)
        {
            Write(false, message);
            StarterController.Toast(message, true);
        }

        private void Info(string message) => Append(Grey, message);

        private void Write(bool ok, string message) => Append(ok ? Green : Red, message);

        private void Append(string color, string message)
        {
            var time = DateTime.Now.ToString("HH:mm:ss", CultureInfo.InvariantCulture);
            _lines.Insert(0, "<color=" + color + ">●</color> <color=" + Grey + ">" + time + "</color>  <noparse>" + message + "</noparse>");
            while (_lines.Count > maxLogLines) _lines.RemoveAt(_lines.Count - 1);
            if (logLabel != null) logLabel.text = string.Join("\n", _lines);
            Debug.Log("[Majacloud Test Data] " + message);
        }

        private static string Explain(MajacloudError error)
        {
            if (error.Code == MajacloudErrorCode.NotFound) return error.Message + " Create it in the dashboard first.";
            return error.Message;
        }

        private static string Read(TMP_InputField input, string fallback)
        {
            var value = input != null ? input.text.Trim() : string.Empty;
            return value.Length > 0 ? value : fallback;
        }

        private static string Number(double value) => value.ToString("#,0.##", CultureInfo.InvariantCulture);

        private static string Short(string text)
        {
            if (string.IsNullOrEmpty(text)) return string.Empty;
            return text.Length > 120 ? text.Substring(0, 117) + "…" : text;
        }

        private static void Bind(Button button, UnityAction action)
        {
            if (button != null) button.onClick.AddListener(action);
        }

        private static void SetInteractable(Button button, bool value)
        {
            if (button != null) button.interactable = value;
        }
    }
}
