using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace IndieExchange
{
    internal sealed class DeviceProof : MonoBehaviour
    {
        private static DeviceProof instance;
        private TaskCompletionSource<NativeProof> completion;
        private string pendingId;
        internal static string Installation(string fallback)
        {
#if UNITY_ANDROID && !UNITY_EDITOR
            using (var bridge = new AndroidJavaClass("com.indieexchange.security.IndieIntegrity"))
            using (var sha = System.Security.Cryptography.SHA256.Create())
                return BitConverter.ToString(sha.ComputeHash(Convert.FromBase64String(bridge.CallStatic<string>("publicKey")))).Replace("-", "").ToLowerInvariant();
#else
            return fallback;
#endif
        }
#if UNITY_IOS && !UNITY_EDITOR
        [DllImport("__Internal")] private static extern void IndieAttestRequest(string receiver, string id, string requestHash);
        [DllImport("__Internal")] private static extern void IndieAttestEnrollment(string keyId, bool accepted);
#endif
        internal static async Task<NativeProof> Get(ChallengeResponse challenge, int timeout, CancellationToken token)
        {
            if (instance == null) { var go = new GameObject("IndieExchangeDeviceProof"); DontDestroyOnLoad(go); instance = go.AddComponent<DeviceProof>(); }
            instance.pendingId = Guid.NewGuid().ToString("N");
            var current = instance.completion = new TaskCompletionSource<NativeProof>();
            try
            {
#if UNITY_ANDROID && !UNITY_EDITOR
                using (var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
                using (var activity = player.GetStatic<AndroidJavaObject>("currentActivity"))
                using (var bridge = new AndroidJavaClass("com.indieexchange.security.IndieIntegrity"))
                    bridge.CallStatic("request", activity, instance.gameObject.name, instance.pendingId, challenge.cloudProjectNumber, challenge.requestHash);
#elif UNITY_IOS && !UNITY_EDITOR
                IndieAttestRequest(instance.gameObject.name, instance.pendingId, challenge.requestHash);
#else
                current.TrySetResult(null);
#endif
                var until = Time.realtimeSinceStartupAsDouble + Math.Max(10, timeout);
                while (!current.Task.IsCompleted && !token.IsCancellationRequested && Time.realtimeSinceStartupAsDouble < until) await Task.Yield();
                return current.Task.IsCompleted && !token.IsCancellationRequested ? current.Task.Result : null;
            }
            catch { return null; }
            finally { instance.pendingId = null; instance.completion = null; }
        }

        // Called by native plugins. Late or unrelated callbacks cannot complete a newer request.
        public void Complete(string json)
        {
            try { var result = JsonUtility.FromJson<NativeProof>(json); if (result != null && result.id == pendingId) completion?.TrySetResult(result); }
            catch { completion?.TrySetResult(null); }
        }
        internal static void EnrollmentFinished(string keyId, bool accepted)
        {
#if UNITY_IOS && !UNITY_EDITOR
            IndieAttestEnrollment(keyId, accepted);
#endif
        }
    }
}
