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, cloudProjectNumber; }
    [Serializable] internal sealed class ProofRequest { public string challengeId, payload, proof, keyId, publicKey, signature; public bool enrollment; }
    [Serializable] internal sealed class NativeProof { public string id, proof, keyId, publicKey, signature; public bool enrollment; }
    [Serializable] internal sealed class TicketEvent { public string ticket; }

    internal static class SecureTransport
    {
        // Serializing proofs avoids App Attest assertion counters arriving out of order.
        private static readonly SemaphoreSlim gate = new SemaphoreSlim(1, 1);
#if UNITY_EDITOR
        internal static Func<string, string, int, int, CancellationToken, Task<HttpResult>> TestTransport;
#endif
        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);
            return new HttpResult(); // Editor cannot attest a real mobile app; test cards remain local.
#else
            if (Application.platform != RuntimePlatform.Android && Application.platform != RuntimePlatform.IPhonePlayer) return new HttpResult();
            var acquired = false;
            try
            {
                await gate.WaitAsync(token); acquired = true;
                var platform = Application.platform == RuntimePlatform.Android ? "android" : "ios";
                installation = DeviceProof.Installation(installation);
                var challenge = await AdTransport.Send(settings.apiBaseUrl + "/api/sdk/v2/challenge", JsonUtility.ToJson(new ChallengeRequest {
                    gameId = settings.gameId, platform = platform, installation = installation, 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 || string.IsNullOrEmpty(data.requestHash) || string.IsNullOrEmpty(data.challengeId)) return new HttpResult();
                var proof = await DeviceProof.Get(data, settings.networkTimeoutSeconds, token);
                if (proof == null || string.IsNullOrEmpty(proof.proof)) return new HttpResult();
                var result = await AdTransport.Send(settings.apiBaseUrl + "/api/sdk/v2/" + action, JsonUtility.ToJson(new ProofRequest {
                    challengeId = data.challengeId, payload = payload, proof = proof.proof, keyId = proof.keyId,
                    publicKey = proof.publicKey, signature = proof.signature, enrollment = proof.enrollment
                }), 65536, settings.networkTimeoutSeconds, token);
                if (proof.enrollment || platform == "ios" && result.status == 409)
                    DeviceProof.EnrollmentFinished(proof.keyId, result.Ok);
                return result;
            }
            catch { return new HttpResult(); }
            finally { if (acquired) gate.Release(); }
#endif
        }
    }
}
