using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MajacloudSdk.Internal;

namespace MajacloudSdk
{
    public sealed class MajacloudLeaderboards
    {
        private readonly MajacloudHttp _http;

        internal MajacloudLeaderboards(MajacloudHttp http)
        {
            _http = http;
        }

        /// <summary>Top entries. Signed-in players get <c>IsMe</c> flags.</summary>
        public async Task<MajacloudResult<MajacloudLeaderboardPage>> GetTopAsync(string slug, int limit = 10, int offset = 0, MajacloudLeaderboardPeriod period = MajacloudLeaderboardPeriod.Current, CancellationToken cancellationToken = default)
        {
            if (!MajacloudEndpoints.IsValidIdentifier(slug)) return MajacloudResult<MajacloudLeaderboardPage>.Fail(MajacloudErrorCode.Validation, "Invalid leaderboard slug.");
            limit = Math.Max(1, Math.Min(100, limit));
            offset = Math.Max(0, offset);
            var query = MajacloudHttp.Query(
                new KeyValuePair<string, string>("limit", limit.ToString(CultureInfo.InvariantCulture)),
                new KeyValuePair<string, string>("offset", offset.ToString(CultureInfo.InvariantCulture)),
                new KeyValuePair<string, string>("period", period == MajacloudLeaderboardPeriod.Previous ? "previous" : "current"));
            var result = await _http.SendJsonAsync(new HttpRequest { Method = "GET", Path = MajacloudEndpoints.LeaderboardEntries(slug), Query = query, Auth = HttpAuth.BearerIfAvailable, Idempotent = true }, cancellationToken);
            if (!result.Ok) return MajacloudResult<MajacloudLeaderboardPage>.Fail(result.Error);
            var json = result.Value;
            var entries = new List<MajacloudLeaderboardEntry>();
            foreach (var node in json.GetArray("entries").Elements()) entries.Add(MajacloudLeaderboardEntry.FromJson(node));
            return MajacloudResult<MajacloudLeaderboardPage>.Success(new MajacloudLeaderboardPage
            {
                Leaderboard = MajacloudLeaderboardInfo.FromJson(json.GetObject("leaderboard")),
                Period = MajacloudPeriodInfo.FromJson(json.GetObject("period")),
                Entries = entries,
                Total = json.GetInt("total"),
                Limit = json.GetInt("limit", limit),
                Offset = json.GetInt("offset", offset),
                MaxEntries = json.GetNullableInt("max_entries"),
            });
        }

        /// <summary>Submit a score for the signed-in player. The server decides whether it beats the previous one.</summary>
        public async Task<MajacloudResult<MajacloudScoreSubmission>> SubmitScoreAsync(string slug, double score, CancellationToken cancellationToken = default)
        {
            if (!MajacloudEndpoints.IsValidIdentifier(slug)) return MajacloudResult<MajacloudScoreSubmission>.Fail(MajacloudErrorCode.Validation, "Invalid leaderboard slug.");
            if (double.IsNaN(score) || double.IsInfinity(score)) return MajacloudResult<MajacloudScoreSubmission>.Fail(MajacloudErrorCode.Validation, "Score must be a finite number.");
            var result = await _http.SendJsonAsync(new HttpRequest { Method = "POST", Path = MajacloudEndpoints.LeaderboardScores(slug), Auth = HttpAuth.Bearer, JsonBody = JsonNode.Object().Set("score", score).ToJson(), Idempotent = false }, cancellationToken);
            if (!result.Ok) return MajacloudResult<MajacloudScoreSubmission>.Fail(result.Error);
            return MajacloudResult<MajacloudScoreSubmission>.Success(new MajacloudScoreSubmission
            {
                Accepted = result.Value.GetBool("accepted"),
                Entry = MajacloudLeaderboardEntry.FromJson(result.Value.GetObject("entry")),
                Period = MajacloudPeriodInfo.FromJson(result.Value.GetObject("period")),
            });
        }

        public async Task<MajacloudResult<MajacloudRankInfo>> GetMyRankAsync(string slug, MajacloudLeaderboardPeriod period = MajacloudLeaderboardPeriod.Current, CancellationToken cancellationToken = default)
        {
            if (!MajacloudEndpoints.IsValidIdentifier(slug)) return MajacloudResult<MajacloudRankInfo>.Fail(MajacloudErrorCode.Validation, "Invalid leaderboard slug.");
            var query = MajacloudHttp.Query(new KeyValuePair<string, string>("period", period == MajacloudLeaderboardPeriod.Previous ? "previous" : "current"));
            var result = await _http.SendJsonAsync(new HttpRequest { Method = "GET", Path = MajacloudEndpoints.LeaderboardMe(slug), Query = query, Auth = HttpAuth.Bearer, Idempotent = true }, cancellationToken);
            if (!result.Ok) return MajacloudResult<MajacloudRankInfo>.Fail(result.Error);
            return MajacloudResult<MajacloudRankInfo>.Success(new MajacloudRankInfo
            {
                Entry = MajacloudLeaderboardEntry.FromJson(result.Value.GetObject("entry")),
                Total = result.Value.GetInt("total"),
                Period = MajacloudPeriodInfo.FromJson(result.Value.GetObject("period")),
            });
        }

        public async Task<MajacloudResult<MajacloudAroundMe>> GetAroundMeAsync(string slug, int radius = 3, CancellationToken cancellationToken = default)
        {
            if (!MajacloudEndpoints.IsValidIdentifier(slug)) return MajacloudResult<MajacloudAroundMe>.Fail(MajacloudErrorCode.Validation, "Invalid leaderboard slug.");
            radius = Math.Max(1, Math.Min(25, radius));
            var query = MajacloudHttp.Query(new KeyValuePair<string, string>("radius", radius.ToString(CultureInfo.InvariantCulture)));
            var result = await _http.SendJsonAsync(new HttpRequest { Method = "GET", Path = MajacloudEndpoints.LeaderboardAroundMe(slug), Query = query, Auth = HttpAuth.Bearer, Idempotent = true }, cancellationToken);
            if (!result.Ok) return MajacloudResult<MajacloudAroundMe>.Fail(result.Error);
            var entries = new List<MajacloudLeaderboardEntry>();
            foreach (var node in result.Value.GetArray("entries").Elements()) entries.Add(MajacloudLeaderboardEntry.FromJson(node));
            return MajacloudResult<MajacloudAroundMe>.Success(new MajacloudAroundMe
            {
                Entries = entries,
                Me = MajacloudLeaderboardEntry.FromJson(result.Value.GetObject("me")),
                Total = result.Value.GetInt("total"),
                Period = MajacloudPeriodInfo.FromJson(result.Value.GetObject("period")),
            });
        }

        // --- Coroutine variants -------------------------------------------------------------

        public IEnumerator GetTop(string slug, int limit, Action<MajacloudResult<MajacloudLeaderboardPage>> callback) => MajacloudCoroutine.Run(GetTopAsync(slug, limit), callback);
        public IEnumerator GetTop(string slug, int limit, int offset, MajacloudLeaderboardPeriod period, Action<MajacloudResult<MajacloudLeaderboardPage>> callback) => MajacloudCoroutine.Run(GetTopAsync(slug, limit, offset, period), callback);
        public IEnumerator SubmitScore(string slug, double score, Action<MajacloudResult<MajacloudScoreSubmission>> callback) => MajacloudCoroutine.Run(SubmitScoreAsync(slug, score), callback);
        public IEnumerator GetMyRank(string slug, Action<MajacloudResult<MajacloudRankInfo>> callback) => MajacloudCoroutine.Run(GetMyRankAsync(slug), callback);
        public IEnumerator GetAroundMe(string slug, int radius, Action<MajacloudResult<MajacloudAroundMe>> callback) => MajacloudCoroutine.Run(GetAroundMeAsync(slug, radius), callback);
    }
}
