#if DAVANCI_IAP
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Purchasing;
#if NEXUS_IAP_VALIDATE
using UnityEngine.Purchasing.Security;
#endif

namespace Davanci.Nexus
{
    public sealed class NexusIapProvider : INexusIapProvider
    {
        StoreController _store;
        IReadOnlyList<NexusProduct> _products;

        readonly HashSet<string> _ownedFromStore = new HashSet<string>();

        readonly Dictionary<string, Action<NexusPurchaseResult>> _pending =
            new Dictionary<string, Action<NexusPurchaseResult>>();

        Action<bool> _onReady;
        Action<bool> _onRestore;
        bool _ready;

        public bool IsReady => _ready;

        public void Initialize(IReadOnlyList<NexusProduct> products, Action<bool> onReady)
        {
            _products = products;
            _onReady = onReady;

            if (_store != null) { onReady?.Invoke(_ready); return; }

            _store = UnityIAPServices.StoreController();
            _store.OnProductsFetched += OnProductsFetched;
            _store.OnProductsFetchFailed += OnProductsFetchFailed;
            _store.OnPurchasePending += OnPurchasePending;
            _store.OnPurchaseConfirmed += OnPurchaseConfirmed;
            _store.OnPurchaseFailed += OnPurchaseFailed;
            _store.OnPurchasesFetched += OnPurchasesFetched;
            _store.OnPurchasesFetchFailed += OnPurchasesFetchFailed;

            ConnectAndFetch();
        }

        async void ConnectAndFetch()
        {
            try
            {
                await _store.Connect();
            }
            catch (Exception e)
            {
                FailInit($"Store connect failed: {e.Message}");
                return;
            }

            var defs = new List<ProductDefinition>();
            if (_products != null)
                foreach (var p in _products)
                    if (p != null && !string.IsNullOrWhiteSpace(p.id))
                        defs.Add(new ProductDefinition(p.id.Trim(), Map(p.kind)));

            if (defs.Count == 0)
            {
                FailInit("No product IDs in NexusConfig.iapProducts — add them in Nexus Setup → Monetization.");
                return;
            }

            _store.FetchProducts(defs);
        }

        public void Buy(string productId, Action<NexusPurchaseResult> onResult)
        {
            if (!_ready) { onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.NotInitialized)); return; }

            var product = _store.GetProductById(productId);
            if (product == null || !product.availableToPurchase)
            {
                onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.UnknownProduct,
                    "Product not found or not available. Check it exists in the store console."));
                return;
            }

            _pending[productId] = onResult;
            _store.PurchaseProduct(product);
        }

        public void Restore(Action<bool> onDone)
        {
            if (!_ready) { onDone?.Invoke(false); return; }
            _onRestore = onDone;

            _store.RestoreTransactions((ok, _) =>
            {
                if (ok) _store.FetchPurchases();   // OnPurchasesFetched fires _onRestore
                else FinishRestore(false);
            });
        }

        public bool HasReceipt(string productId) => _ownedFromStore.Contains(productId);

        public NexusIapPrice GetPrice(string productId)
        {
            var meta = _store?.GetProductById(productId)?.metadata;
            if (meta == null) return NexusIapPrice.Unavailable;
            return new NexusIapPrice
            {
                Amount = (double)meta.localizedPrice,
                Currency = meta.isoCurrencyCode ?? "",
                Display = meta.localizedPriceString ?? "",
            };
        }

        public string GetPriceString(string productId) => GetPrice(productId).Display;

        static ProductType Map(NexusProductKind kind)
            => kind == NexusProductKind.NonConsumable ? ProductType.NonConsumable : ProductType.Consumable;

        // ---- product fetch -------------------------------------------------------------

        void OnProductsFetched(List<Product> products)
        {

            _store.FetchPurchases();
        }

        void OnProductsFetchFailed(ProductFetchFailed fail)
            => FailInit($"Product fetch failed: {fail?.FailureReason}");

        void FailInit(string message)
        {
            Debug.LogWarning($"[Nexus] IAP init failed: {message}");
            _ready = false;
            var cb = _onReady; _onReady = null;
            cb?.Invoke(false);
        }

        // ---- purchases fetch (ownership) -----------------------------------------------

        void OnPurchasesFetched(Orders orders)
        {
            _ownedFromStore.Clear();
            if (orders != null)
            {
                AddOwned(orders.ConfirmedOrders);
                AddOwned(orders.PendingOrders);
            }

            if (_onReady != null)
            {
                _ready = true;
                var cb = _onReady; _onReady = null;
                cb.Invoke(true);
            }
            else
            {
                FinishRestore(true);
            }
        }

        void OnPurchasesFetchFailed(PurchasesFetchFailureDescription fail)
        {
            if (_onReady != null)
            {
                _ready = true;
                var cb = _onReady; _onReady = null;
                cb.Invoke(true);
            }
            else
            {
                FinishRestore(false);
            }
        }

        void AddOwned<T>(IReadOnlyList<T> orders) where T : Order
        {
            if (orders == null) return;
            foreach (var order in orders)
            {
                string id = FirstProductId(order);
                if (!string.IsNullOrEmpty(id)) _ownedFromStore.Add(id);
            }
        }

        void FinishRestore(bool ok)
        {
            var cb = _onRestore; _onRestore = null;
            cb?.Invoke(ok);
        }

        // ---- purchase flow -------------------------------------------------------------

        void OnPurchasePending(PendingOrder order)
        {
            string id = FirstProductId(order);

            if (!ValidateReceipt(order, out string reason))
            {
                Resolve(id, NexusPurchaseResult.Fail(id, NexusPurchaseStatus.InvalidReceipt, reason));
                _store.ConfirmPurchase(order); // clear the bad transaction
                return;
            }

            _ownedFromStore.Add(id);

            var product = _store.GetProductById(id);
            double price = product != null ? (double)product.metadata.localizedPrice : 0;
            string currency = product?.metadata?.isoCurrencyCode;

            Resolve(id, new NexusPurchaseResult
            {
                Success = true, ProductId = id, Status = NexusPurchaseStatus.Success,
                Price = price, Currency = currency,
            });

            _store.ConfirmPurchase(order);
        }

        void OnPurchaseConfirmed(Order order)
        {
            if (order is ConfirmedOrder)
            {
                string id = FirstProductId(order);
                if (!string.IsNullOrEmpty(id)) _ownedFromStore.Add(id);
            }
        }

        void OnPurchaseFailed(FailedOrder order)
        {
            string id = FirstProductId(order);
            var status = order.FailureReason == PurchaseFailureReason.UserCancelled
                ? NexusPurchaseStatus.Cancelled
                : NexusPurchaseStatus.Failed;
            Resolve(id, NexusPurchaseResult.Fail(id, status, order.Details));
        }

        void Resolve(string productId, NexusPurchaseResult result)
        {
            if (productId != null && _pending.TryGetValue(productId, out var cb))
            {
                _pending.Remove(productId);
                cb?.Invoke(result);
            }
        }

        static string FirstProductId(Order order)
        {
            var items = order?.CartOrdered?.Items();
            if (items == null || items.Count == 0) return null;
            return items[0].Product?.definition?.id;
        }

        // ---- receipt validation --------------------------------------------------------

        bool ValidateReceipt(Order order, out string reason)
        {
            reason = "";
#if NEXUS_IAP_VALIDATE && (UNITY_IOS || UNITY_ANDROID || UNITY_STANDALONE_OSX || UNITY_TVOS)
            byte[] google = TangleData("GooglePlayTangle");
            byte[] apple = TangleData("AppleTangle");
            if (google == null && apple == null)
            {
                Debug.LogWarning("[Nexus] Receipt validation on but no tangle files found. " +
                    "Generate them: Nexus Setup → IAP → Regenerate receipt tangles. Skipping for now.");
                return true;
            }

            string receipt = order?.Info?.Receipt;
            if (string.IsNullOrEmpty(receipt))
            {
                // StoreKit 2 / TestFlight can finish a purchase before an app receipt is available.
                Debug.LogWarning("[Nexus] Receipt validation skipped — order has no receipt yet.");
                return true;
            }

            try
            {
                var validator = new CrossPlatformValidator(
                    google, apple, Application.identifier, Application.identifier);
                validator.Validate(receipt);
                return true;
            }
            catch (IAPSecurityException e)
            {
                reason = $"Receipt validation failed: {e.Message}";
                Debug.LogWarning($"[Nexus] {reason}");
                return false;
            }
#else
            return true;
#endif
        }

#if NEXUS_IAP_VALIDATE && (UNITY_IOS || UNITY_ANDROID || UNITY_STANDALONE_OSX || UNITY_TVOS)
        static byte[] TangleData(string typeName)
        {
            var t = FindTangleType(typeName);
            var m = t?.GetMethod("Data", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
            return m?.Invoke(null, null) as byte[];
        }

        static Type FindTangleType(string typeName)
        {
            // Unity IAP writes these under UnityEngine.Purchasing.Security (not the global namespace).
            string full = "UnityEngine.Purchasing.Security." + typeName;
            var t = Type.GetType(full) ?? Type.GetType(typeName);
            if (t != null) return t;

            var asms = AppDomain.CurrentDomain.GetAssemblies();
            for (int i = 0; i < asms.Length; i++)
            {
                t = asms[i].GetType(full) ?? asms[i].GetType(typeName);
                if (t != null) return t;
            }
            return null;
        }
#endif
    }
}
#endif
