using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

namespace MJGameSDK
{
    public class MJGameHttpClient
    {
        [ThreadStatic]
        private static string _currentRequestId;
        public static string CurrentRequestId => _currentRequestId;

        private readonly string _baseUrl;
        private readonly int _timeout;

        private bool _refreshInFlight;
        private readonly List<Action<bool, string>> _refreshWaiters = new List<Action<bool, string>>();

        public string AccessToken => MJGameAuthSession.AccessToken;

        public MJGameHttpClient(string baseUrl, int timeout = 30)
        {
            _baseUrl = baseUrl.TrimEnd('/');
            _timeout = timeout;
        }

        public void SetAccessToken(string token)
        {
            if (string.IsNullOrEmpty(token))
            {
                MJGameAuthSession.Clear();
                MJGameClientContext.Instance?.ClearUserId();
                return;
            }

            // 登录流程已由 ApplyLogin 写入完整会话；此处仅同步 access_token
            if (MJGameAuthSession.AccessToken != token)
            {
                MJGameAuthSession.ApplyRefresh(
                    token,
                    (int)Math.Max(60, (MJGameAuthSession.AccessExpiresAtUtc - DateTime.UtcNow).TotalSeconds),
                    MJGameAuthSession.RequestKey,
                    (int)Math.Max(0, (MJGameAuthSession.RequestKeyExpiresAtUtc - DateTime.UtcNow).TotalSeconds));
            }
        }

        public void SetUserId(int userId)
        {
            MJGameClientContext.Instance?.SetUserId(userId);
        }

        public void Get<T>(string path, Action<T> onSuccess, Action<MJGameResult> onError) where T : MJGameResponse
        {
            SendRequest(path, "GET", null, onSuccess, onError);
        }

        public void Post<T>(string path, object body, Action<T> onSuccess, Action<MJGameResult> onError) where T : MJGameResponse
        {
            var json = body != null ? JsonUtility.ToJson(body) : "{}";
            var merged = MJGameClientContext.Instance?.MergeIntoJsonBody(json) ?? json;
            SendRequest(path, "POST", merged, onSuccess, onError);
        }

        private void SendRequest<T>(string path, string method, string body, Action<T> onSuccess, Action<MJGameResult> onError) where T : MJGameResponse
        {
            MJGameSDK.Runner.StartCoroutine(SendRequestRoutine(path, method, body, onSuccess, onError, false));
        }

        private IEnumerator SendRequestRoutine<T>(string path, string method, string body, Action<T> onSuccess, Action<MJGameResult> onError, bool isRetry) where T : MJGameResponse
        {
            path = path.TrimStart('/');
            var needsAuth = RequiresAuth(path);
            var needsSign = RequiresSign(path);

            if (needsAuth && MJGameAuthSession.NeedsRefresh())
            {
                var refreshDone = false;
                var refreshOk = false;
                string refreshErr = null;
                YieldRefresh((ok, err) =>
                {
                    refreshOk = ok;
                    refreshErr = err;
                    refreshDone = true;
                });
                while (!refreshDone)
                    yield return null;

                if (!refreshOk)
                {
                    onError?.Invoke(MJGameResult.Fail(2002, refreshErr ?? "Token refresh failed"));
                    yield break;
                }
            }

            if (needsSign && !MJGameAuthSession.HasRequestKey)
            {
                onError?.Invoke(MJGameResult.Fail(2013, "Request key missing, please login again"));
                yield break;
            }

            var requestId = Guid.NewGuid().ToString("N").Substring(0, 12);
            _currentRequestId = requestId;
            var url = $"{_baseUrl}/{path}";

            if (method == "GET" && MJGameClientContext.Instance != null)
                url = MJGameClientContext.Instance.AppendQueryString(url);

            // 签名 path 不含 query（与服务端 apiRelativePath 一致）
            var signPath = path;
            var signBody = method == "GET" ? "" : (body ?? "");
            string timestamp = null;
            string nonce = null;
            string sign = null;
            if (needsSign)
            {
                timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
                nonce = MJGameAuthSession.NewNonce();
                var raw = MJGameAuthSession.BuildSignRaw(method, signPath, timestamp, nonce, signBody);
                sign = MJGameAuthSession.SignHmacSha256(raw, MJGameAuthSession.RequestKey);
            }

            MJGameLogger.Debug("Http", $"{method} {url} body={body ?? "null"}");

            using (var request = new UnityWebRequest(url, method))
            {
                if (!string.IsNullOrEmpty(body) && method != "GET")
                {
                    var rawBytes = Encoding.UTF8.GetBytes(body);
                    request.uploadHandler = new UploadHandlerRaw(rawBytes);
                    request.SetRequestHeader("Content-Type", "application/json");
                }
                request.downloadHandler = new DownloadHandlerBuffer();
                request.timeout = _timeout;
                request.SetRequestHeader("Accept", "application/json");
                request.SetRequestHeader("X-Request-Id", requestId);

                if (!string.IsNullOrEmpty(MJGameAuthSession.AccessToken))
                    request.SetRequestHeader("Authorization", $"Bearer {MJGameAuthSession.AccessToken}");

                if (needsSign)
                {
                    request.SetRequestHeader("X-Timestamp", timestamp);
                    request.SetRequestHeader("X-Nonce", nonce);
                    request.SetRequestHeader("X-Sign", sign);
                }

                MJGameClientContext.Instance?.ApplyHeaders(request);

                yield return request.SendWebRequest();

                var responseText = request.downloadHandler?.text ?? "";
                MJGameLogger.Debug("Http", $"Response [{request.responseCode}]: {responseText}");

                if (request.result != UnityWebRequest.Result.Success)
                {
                    onError?.Invoke(MJGameResult.Fail(1001, $"Network error: {request.error}"));
                    yield break;
                }

                // request_key 过期：自动 refresh 一次后重试（仅一次）
                if (!isRetry && needsSign && request.responseCode == 401
                    && (responseText.IndexOf("\"code\":2013", StringComparison.Ordinal) >= 0
                        || responseText.IndexOf("\"code\": 2013", StringComparison.Ordinal) >= 0))
                {
                    var retryDone = false;
                    var retryOk = false;
                    string retryErr = null;
                    YieldRefresh((ok, err) =>
                    {
                        retryOk = ok;
                        retryErr = err;
                        retryDone = true;
                    });
                    while (!retryDone)
                        yield return null;

                    if (!retryOk)
                    {
                        onError?.Invoke(MJGameResult.Fail(2002, retryErr ?? "Token refresh failed"));
                        yield break;
                    }

                    yield return SendRequestRoutine(path, method, body, onSuccess, onError, true);
                    yield break;
                }

                T response = null;
                try
                {
                    response = JsonUtility.FromJson<T>(responseText);
                }
                catch (Exception e)
                {
                    onError?.Invoke(MJGameResult.Fail(1002, $"Parse error: {e.Message}"));
                    yield break;
                }

                if (response == null)
                {
                    onError?.Invoke(MJGameResult.Fail(1002, "Invalid response"));
                    yield break;
                }

                if (response.code != 0)
                {
                    onError?.Invoke(MJGameResult.Fail(response.code, response.message));
                    yield break;
                }

                onSuccess?.Invoke(response);
            }
        }

        private void YieldRefresh(Action<bool, string> done)
        {
            _refreshWaiters.Add(done);
            if (_refreshInFlight)
                return;

            _refreshInFlight = true;
            MJGameSDK.Runner.StartCoroutine(DoRefresh());
        }

        private IEnumerator DoRefresh()
        {
            if (string.IsNullOrEmpty(MJGameAuthSession.AccessToken))
            {
                FinishRefresh(false, "Not logged in");
                yield break;
            }

            var url = $"{_baseUrl}/auth/refresh";
            using (var request = new UnityWebRequest(url, "POST"))
            {
                var rawBytes = Encoding.UTF8.GetBytes("{}");
                request.uploadHandler = new UploadHandlerRaw(rawBytes);
                request.downloadHandler = new DownloadHandlerBuffer();
                request.timeout = _timeout;
                request.SetRequestHeader("Content-Type", "application/json");
                request.SetRequestHeader("Accept", "application/json");
                request.SetRequestHeader("Authorization", $"Bearer {MJGameAuthSession.AccessToken}");
                MJGameClientContext.Instance?.ApplyHeaders(request);

                yield return request.SendWebRequest();

                var text = request.downloadHandler?.text ?? "";
                if (request.result != UnityWebRequest.Result.Success)
                {
                    FinishRefresh(false, $"Network error: {request.error}");
                    yield break;
                }

                TokenRefreshResponse parsed = null;
                try
                {
                    parsed = JsonUtility.FromJson<TokenRefreshResponse>(text);
                }
                catch (Exception e)
                {
                    FinishRefresh(false, $"Parse error: {e.Message}");
                    yield break;
                }

                if (parsed == null || parsed.code != 0 || parsed.data == null || string.IsNullOrEmpty(parsed.data.access_token))
                {
                    FinishRefresh(false, parsed != null ? parsed.message : "Token refresh failed");
                    yield break;
                }

                MJGameAuthSession.ApplyRefresh(
                    parsed.data.access_token,
                    parsed.data.expires_in > 0 ? parsed.data.expires_in : 7200,
                    parsed.data.request_key,
                    parsed.data.request_key_expires_in);

                MJGameLogger.Info("Auth", "Token refreshed (access + request_key)");
                FinishRefresh(true, null);
            }
        }

        private void FinishRefresh(bool ok, string error)
        {
            _refreshInFlight = false;
            var waiters = new List<Action<bool, string>>(_refreshWaiters);
            _refreshWaiters.Clear();
            foreach (var w in waiters)
                w?.Invoke(ok, error);
        }

        private static bool RequiresAuth(string path)
        {
            if (string.IsNullOrEmpty(path))
                return false;
            if (path.StartsWith("auth/steam/login"))
                return false;
            if (path.StartsWith("events/game-activate"))
                return false;
            if (path.StartsWith("events/ui-stage"))
                return false;
            if (path.StartsWith("legal/privacy") && !path.Contains("accept"))
                return false;
            if (path.StartsWith("health"))
                return false;
            return true;
        }

        private static bool RequiresSign(string path)
        {
            if (string.IsNullOrEmpty(path))
                return false;
            if (path.StartsWith("auth/steam/login") || path.StartsWith("auth/refresh"))
                return false;
            if (path.StartsWith("events/game-activate"))
                return false;
            if (path.StartsWith("events/ui-stage"))
                return false;
            if (path.StartsWith("legal/privacy") && !path.Contains("accept"))
                return false;
            return RequiresAuth(path);
        }
    }

    [Serializable]
    public class TokenRefreshResponse : MJGameResponse
    {
        public TokenRefreshData data;
    }

    [Serializable]
    public class TokenRefreshData
    {
        public string access_token;
        public int expires_in;
        public string request_key;
        public int request_key_expires_in;
    }
}
