using System;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

namespace CoolGameX
{
    /// <summary>Result of a CoolAPI HTTP call.</summary>
    public struct ApiResult
    {
        public bool Success;
        public long StatusCode;
        public string Body;
        /// <summary>A readable error: the server's message when it sent one.</summary>
        public string Error;
        /// <summary>The request never reached the server (offline, DNS, timeout…).</summary>
        public bool IsNetworkError;
        /// <summary>The server hit an internal error (HTTP 5xx).</summary>
        public bool IsServerError => StatusCode >= 500;
        /// <summary>Worth retrying later.</summary>
        public bool IsTransient => IsNetworkError || IsServerError;
    }

    /// <summary>
    /// HTTP helper built on UnityWebRequest (awaitable on the main thread).
    /// Retries transient failures, refreshes expiring game tokens transparently and
    /// keeps <see cref="CoolAPI.IsConnected"/> up to date.
    /// </summary>
    internal static class Net
    {
        /// <summary>Request timeout in seconds (applied to all calls).</summary>
        public static int TimeoutSeconds = 20;
        /// <summary>Extra attempts for idempotent calls when the network or server hiccups.</summary>
        public static int MaxRetries = 2;

        public static Task<ApiResult> Get(string path, bool auth = false, bool retry = true) => Send("GET", path, null, auth, retry);
        public static Task<ApiResult> PostJson(string path, string json, bool auth = true, bool retry = false) => Send("POST", path, json ?? "{}", auth, retry);
        public static Task<ApiResult> PutJson(string path, string json, bool auth = true, bool retry = true) => Send("PUT", path, json ?? "{}", auth, retry);
        public static Task<ApiResult> Delete(string path, bool auth = true, bool retry = true) => Send("DELETE", path, null, auth, retry);

        private static async Task<ApiResult> Send(string method, string path, string body, bool auth, bool retry)
        {
            CoolAPI.EnsureReady();
            bool refreshed = false;
            int attempt = 0;
            while (true)
            {
                if (auth && string.IsNullOrEmpty(CoolAPI.Token))
                    return new ApiResult { Success = false, StatusCode = 401, Error = "Not signed in" };

                var r = await Raw(method, path, body, auth);
                if (r.Success) return r;

                // Expired game token → refresh once and replay the request.
                if (auth && r.StatusCode == 401 && !refreshed && CoolAPI.Auth != null && CoolAPI.Auth.IsGameToken)
                {
                    refreshed = true;
                    if (await CoolAPI.Auth.Refresh()) continue;
                }

                if (retry && r.IsTransient && attempt < MaxRetries)
                {
                    attempt++;
                    await Task.Delay(attempt == 1 ? 600 : 2000);
                    continue;
                }
                return r;
            }
        }

        /// <summary>One request, no retries or refresh (used by token refresh itself).</summary>
        internal static async Task<ApiResult> Raw(string method, string path, string body, bool auth)
        {
            using var req = new UnityWebRequest(CoolAPI.ServerUrl + path, method);
            req.downloadHandler = new DownloadHandlerBuffer();
            if (body != null)
            {
                req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body));
                req.SetRequestHeader("Content-Type", "application/json");
            }
            req.timeout = TimeoutSeconds;
            req.SetRequestHeader("X-CGX-SDK", CoolAPI.Version);
            if (auth && !string.IsNullOrEmpty(CoolAPI.Token))
                req.SetRequestHeader("Authorization", "Bearer " + CoolAPI.Token);

            await req.SendWebRequest();
            return ToResult(req, method, path);
        }

        private static ApiResult ToResult(UnityWebRequest req, string method, string path)
        {
            bool ok = req.result == UnityWebRequest.Result.Success;
            bool network = req.result == UnityWebRequest.Result.ConnectionError;
            string body = req.downloadHandler?.text;

            CoolAPI.LastStatus = req.responseCode;
            CoolAPI.SetConnected(!network);

            string error = null;
            if (!ok)
            {
                error = Json.ErrorOf(body) ?? req.error ?? "Request failed";
                CoolAPI.LastError = network ? $"Offline: {error}" : $"HTTP {req.responseCode}: {error}";
                if (network) Log.Verbose($"{method} {path} — can't reach {CoolAPI.ServerUrl} ({req.error})");
                else Log.Warn($"{method} {path} → {req.responseCode} {error}");
            }
            else
            {
                Log.Verbose($"{method} {path} → {req.responseCode}");
            }

            return new ApiResult
            {
                Success = ok,
                StatusCode = req.responseCode,
                Body = body,
                Error = error,
                IsNetworkError = network,
            };
        }
    }

    /// <summary>Makes UnityWebRequestAsyncOperation awaitable: <c>await req.SendWebRequest()</c>.</summary>
    internal static class UnityWebRequestAwaiterExtensions
    {
        public static UnityWebRequestAwaiter GetAwaiter(this UnityWebRequestAsyncOperation op)
            => new UnityWebRequestAwaiter(op);
    }

    internal readonly struct UnityWebRequestAwaiter : INotifyCompletion
    {
        private readonly UnityWebRequestAsyncOperation _op;

        public UnityWebRequestAwaiter(UnityWebRequestAsyncOperation op) { _op = op; }

        public bool IsCompleted => _op.isDone;

        public void OnCompleted(Action continuation) => _op.completed += _ => continuation();

        public void GetResult() { }
    }
}
