using System;
using System.Collections.Generic;
using UnityEngine;

namespace Davanci.Nexus
{
    // In-app purchases, game-agnostic. The store layer (Unity IAP) is hidden behind
    // INexusIapProvider; this core owns the parts that don't depend on a store: ownership
    // tracking for non-consumables, persistence so OwnsProduct survives restarts, and the
    // automatic analytics hook (a verified purchase logs through DavanciNexus.Purchase).
    //
    // Injectable provider + persistence so the whole flow runs deterministically in the
    // editor against FakeIapProvider — see NexusIapSelfTest.
    public sealed class NexusIapCore
    {
        readonly INexusIapProvider _provider;
        readonly List<NexusProduct> _products;
        readonly HashSet<string> _owned = new HashSet<string>();
        readonly Func<string> _loadOwned;
        readonly Action<string> _saveOwned;
        bool _ready;

        const string OwnedPrefKey = "nexus_iap_owned";

        public NexusIapCore(INexusIapProvider provider, IEnumerable<NexusProduct> products,
                            Func<string> loadOwned = null, Action<string> saveOwned = null)
        {
            _provider = provider ?? new FakeIapProvider();
            _products = new List<NexusProduct>();
            if (products != null)
                foreach (var p in products)
                    if (p != null && !string.IsNullOrEmpty(p.id))
                        _products.Add(p);

            _loadOwned = loadOwned ?? (() => PlayerPrefs.GetString(OwnedPrefKey, ""));
            _saveOwned = saveOwned ?? (csv => { PlayerPrefs.SetString(OwnedPrefKey, csv); PlayerPrefs.Save(); });

            foreach (var id in _loadOwned().Split(','))
                if (!string.IsNullOrEmpty(id)) _owned.Add(id);
        }

        public bool IsReady => _ready;
        public IReadOnlyList<NexusProduct> Products => _products;

        public void Initialize(Action<bool> onReady = null)
        {
            _provider.Initialize(_products, ok =>
            {
                _ready = ok;
                if (ok) SyncOwnershipFromStore();
                onReady?.Invoke(ok);
            });
        }

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

            var product = Find(productId);
            if (product == null) { onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.UnknownProduct)); return; }

            // Owning a non-consumable already? Deliver it again without re-charging or re-logging.
            if (product.kind == NexusProductKind.NonConsumable && OwnsProduct(productId))
            {
                onResult?.Invoke(new NexusPurchaseResult
                {
                    Success = true, ProductId = productId, Status = NexusPurchaseStatus.AlreadyOwned,
                });
                return;
            }

            _provider.Buy(productId, r =>
            {
                if (r.Success)
                {
                    if (product.kind == NexusProductKind.NonConsumable) Grant(productId);
                    ReportPurchase(r);
                }
                onResult?.Invoke(r);
            });
        }

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

        public bool OwnsProduct(string productId)
            => _owned.Contains(productId) || (_ready && _provider.HasReceipt(productId));

        public NexusIapPrice GetPrice(string productId)
            => _ready ? _provider.GetPrice(productId) : NexusIapPrice.Unavailable;

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

        // Pull every non-consumable the store says we own into the local set (covers reinstall
        // and cross-device restore, where the store has the receipt but our prefs don't yet).
        void SyncOwnershipFromStore()
        {
            bool changed = false;
            foreach (var p in _products)
                if (p.kind == NexusProductKind.NonConsumable && _provider.HasReceipt(p.id) && _owned.Add(p.id))
                    changed = true;
            if (changed) Persist();
        }

        void Grant(string productId)
        {
            if (_owned.Add(productId)) Persist();
        }

        void Persist() => _saveOwned(string.Join(",", _owned));

        void ReportPurchase(NexusPurchaseResult r)
        {
            try
            {
                var product = Find(r.ProductId);
                string itemType = product?.kind == NexusProductKind.NonConsumable ? "non_consumable" : "consumable";
                DavanciNexus.Purchase(r.ProductId, r.Price,
                    string.IsNullOrEmpty(r.Currency) ? "USD" : r.Currency, itemType);
            }
            catch (Exception e) { Debug.LogWarning($"[Nexus] IAP analytics failed: {e.Message}"); }
        }

        NexusProduct Find(string id) => _products.Find(p => p.id == id);
    }

    // Thin static facade in the style of NexusCloudSave. Routed through DavanciNexus so games
    // call DavanciNexus.Buy / Restore / OwnsProduct and never touch this type directly.
    public static class NexusIAP
    {
        static NexusIapCore _core;

        // Fires when Initialize() completes (auto-init at startup or manual). False = store connect /
        // product fetch failed — check product IDs in NexusConfig and Davanci Doctor.
        public static event Action<bool> Initialized;

        public static NexusIapCore Core => _core ??= BuildDefault();

        static NexusIapCore BuildDefault()
        {
            var cfg = NexusConfig.Load();
            var products = cfg != null ? cfg.iapProducts : null;
            return new NexusIapCore(SelectProvider(cfg), products);
        }

        static INexusIapProvider SelectProvider(NexusConfig cfg)
        {
#if DAVANCI_IAP
#if UNITY_EDITOR
            if (cfg == null || !cfg.iapUseRealStoreInEditor)
                return new FakeIapProvider();
#endif
            return new NexusIapProvider();
#else
            return new FakeIapProvider();
#endif
        }

        public static bool IsReady => Core.IsReady;
        public static void Initialize(Action<bool> onReady = null)
        {
            Core.Initialize(ok =>
            {
                Initialized?.Invoke(ok);
                onReady?.Invoke(ok);
            });
        }
        public static void Buy(string productId, Action<NexusPurchaseResult> onResult) => Core.Buy(productId, onResult);
        public static void Restore(Action<bool> onDone = null) => Core.Restore(onDone);
        public static bool OwnsProduct(string productId) => Core.OwnsProduct(productId);
        public static NexusIapPrice GetPrice(string productId) => Core.GetPrice(productId);
        public static string PriceString(string productId) => Core.PriceString(productId);

        // Lets tests / tooling swap in a fully controlled core.
        public static void OverrideCoreForTesting(NexusIapCore core) => _core = core;
    }
}
