using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace IndieExchange
{
    [Serializable] internal sealed class ChallengeRequest { public string gameId, platform, installation, action, payload; }
    [Serializable] internal sealed class ChallengeResponse { public string challengeId, requestHash; }
    [Serializable] internal sealed class ProofRequest { public string challengeId, payload, publicKey, signature; }
    [Serializable] internal sealed class TicketEvent { public string ticket; }
    internal static class SecureTransport
    {
        private static readonly SemaphoreSlim gate = new SemaphoreSlim(1, 1);
#if UNITY_EDITOR
        internal static Func<string, string, int, int, CancellationToken, Task<HttpResult>> TestTransport;
#endif
        internal static string Describe(HttpResult result)
        {
            if (result.status == 204) return "No eligible ad. Check active ads and this game's public/personal settings.";
            if (result.status == 404 || result.status == 405) return "Server needs the updated Indie Exchange Web publish (SDK 0.4).";
            if (result.status == 401 || result.status == 403) return "Game unavailable. Check the Game ID and that delivery is enabled on the website.";
            if (result.status == 429) return "Ad request cooldown. Retrying automatically in " + (result.retryAfterSeconds > 0 ? result.retryAfterSeconds : 60) + " seconds.";
            if (result.status >= 500) return "Ad service unavailable. Your other ad provider can still run.";
            if (result.status == 0) return "Could not reach the ad service. Check the connection and restart the app.";
            return "Ad service returned an invalid response (HTTP " + result.status + ").";
        }
        public static async Task<HttpResult> Send(IndieAdsSettings settings, string installation, string action, string payload, CancellationToken token)
        {
#if UNITY_EDITOR
            if (TestTransport != null) return await TestTransport(settings.apiBaseUrl + "/api/sdk/" + action, payload, 65536, settings.networkTimeoutSeconds, token);
            if (!settings.testMode) return new HttpResult { status = 403 };
#endif
            if (settings.testMode && action != "configuration" && action != "ad-request") return new HttpResult { status = 403 };
            var acquired = false;
            try {
                await gate.WaitAsync(token); acquired = true;
                var platform = Application.platform == RuntimePlatform.IPhonePlayer ? "ios" : Application.isEditor ? settings.editorPlatform : "android";
                var endpoint = settings.apiBaseUrl + "/api/sdk/v3/" + (settings.testMode ? "preview/" : "live/");
                var directory = Application.persistentDataPath;
                await Task.Run(() => InstallationKey.Prepare(directory), token);
                var challenge = await AdTransport.Send(endpoint + "challenge", JsonUtility.ToJson(new ChallengeRequest {
                    gameId = settings.gameId, platform = platform, installation = InstallationKey.Id, action = action, payload = payload
                }), 8192, settings.networkTimeoutSeconds, token);
                if (!challenge.Ok) return challenge;
                var data = JsonUtility.FromJson<ChallengeResponse>(Encoding.UTF8.GetString(challenge.body));
                if (data == null || data.challengeId == null || data.challengeId.Length != 64 || string.IsNullOrEmpty(data.requestHash)) return new HttpResult();
                return await AdTransport.Send(endpoint + action, JsonUtility.ToJson(new ProofRequest {
                    challengeId = data.challengeId, payload = payload, publicKey = InstallationKey.PublicKey,
                    signature = InstallationKey.Sign(data.requestHash)
                }), 65536, settings.networkTimeoutSeconds, token);
            }
            catch { return new HttpResult(); }
            finally { if (acquired) gate.Release(); }
        }
    }
}
