using System;
using System.Collections.Generic;

namespace Davanci.Nexus
{
    // In-app purchase seams for NexusIAP. The store layer (Unity IAP) sits behind
    // INexusIapProvider so the whole buy / restore / ownership flow can be exercised in the
    // editor with the FakeIapProvider double — no store account, no device, no build.

    // What kind of product this is. We support the two simplest, most common cases.
    public enum NexusProductKind
    {
        Consumable,     // can be bought repeatedly (coins, gems, refills)
        NonConsumable,  // bought once, persists, must be restorable (remove ads, unlocks)
    }

    // A purchasable item. Mirrors the product IDs you create in App Store Connect / Play.
    [Serializable]
    public class NexusProduct
    {
        public string id;
        public NexusProductKind kind = NexusProductKind.Consumable;
    }

    public enum NexusPurchaseStatus
    {
        Success,         // bought (or restored) and validated
        AlreadyOwned,    // non-consumable the player already owns
        Cancelled,       // player dismissed the store sheet
        Failed,          // store/network error
        NotInitialized,  // IAP not ready yet
        UnknownProduct,  // id not in the catalog
        InvalidReceipt,  // receipt validation rejected it
    }

    public struct NexusPurchaseResult
    {
        public bool Success;
        public string ProductId;
        public NexusPurchaseStatus Status;
        public string Message;
        public double Price;       // localized numeric price, for analytics
        public string Currency;    // ISO currency code, for analytics

        public static NexusPurchaseResult Fail(string id, NexusPurchaseStatus status, string message = "")
            => new NexusPurchaseResult { Success = false, ProductId = id, Status = status, Message = message };
    }

    // Store-localized price for UI. Available after IAP init succeeds (store products fetched).
    public struct NexusIapPrice
    {
        public double Amount;     // numeric localized price, e.g. 0.99
        public string Currency;   // ISO 4217 code, e.g. USD, EUR
        public string Display;    // store-formatted string, e.g. "$0.99", "0,99 €"

        public bool IsAvailable => Amount > 0 || !string.IsNullOrEmpty(Display);

        public static NexusIapPrice Unavailable => default;
    }

    // The store slot. Implementations: Unity IAP (NexusIapProvider) and the editor/test fake.
    public interface INexusIapProvider
    {
        bool IsReady { get; }
        void Initialize(IReadOnlyList<NexusProduct> products, Action<bool> onReady);
        void Buy(string productId, Action<NexusPurchaseResult> onResult);
        void Restore(Action<bool> onDone);
        bool HasReceipt(string productId);            // non-consumable already owned per the store
        NexusIapPrice GetPrice(string productId);     // localized amount + currency + display string
        string GetPriceString(string productId);      // shorthand for GetPrice().Display
    }

    // Editor / test double: instant, deterministic, no store. Mirrors the in-memory doubles
    // used by Cloud Save so the IAP flow can be unit-tested. Configure outcomes via the
    // public knobs before driving a NexusIapCore.
    public sealed class FakeIapProvider : INexusIapProvider
    {
        readonly HashSet<string> _receipts = new HashSet<string>();
        readonly Dictionary<string, NexusProductKind> _kinds = new Dictionary<string, NexusProductKind>();

        public bool IsReady { get; private set; }
        public bool InitShouldSucceed { get; set; } = true;
        public bool CancelNextPurchase { get; set; }   // simulate the player backing out
        public bool FailNextPurchase { get; set; }     // simulate a store error
        public string FakePrice { get; set; } = "$0.99";
        public double FakePriceValue { get; set; } = 0.99;
        public string FakeCurrency { get; set; } = "USD";
        public int BuyCount { get; private set; }
        public int RestoreCount { get; private set; }

        public void Initialize(IReadOnlyList<NexusProduct> products, Action<bool> onReady)
        {
            _kinds.Clear();
            if (products != null)
                foreach (var p in products)
                    if (p != null && !string.IsNullOrEmpty(p.id))
                        _kinds[p.id] = p.kind;
            IsReady = InitShouldSucceed;
            onReady?.Invoke(IsReady);
        }

        public void Buy(string productId, Action<NexusPurchaseResult> onResult)
        {
            BuyCount++;
            if (!IsReady) { onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.NotInitialized)); return; }
            if (!_kinds.ContainsKey(productId)) { onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.UnknownProduct)); return; }
            if (CancelNextPurchase) { CancelNextPurchase = false; onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.Cancelled)); return; }
            if (FailNextPurchase) { FailNextPurchase = false; onResult?.Invoke(NexusPurchaseResult.Fail(productId, NexusPurchaseStatus.Failed)); return; }

            if (_kinds[productId] == NexusProductKind.NonConsumable)
                _receipts.Add(productId);

            onResult?.Invoke(new NexusPurchaseResult
            {
                Success = true,
                ProductId = productId,
                Status = NexusPurchaseStatus.Success,
                Price = FakePriceValue,
                Currency = FakeCurrency,
            });
        }

        public void Restore(Action<bool> onDone)
        {
            RestoreCount++;
            // Pretend the store reports every non-consumable as owned (a generous restore).
            foreach (var kv in _kinds)
                if (kv.Value == NexusProductKind.NonConsumable)
                    _receipts.Add(kv.Key);
            onDone?.Invoke(IsReady);
        }

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

        public NexusIapPrice GetPrice(string productId)
        {
            if (!_kinds.ContainsKey(productId)) return NexusIapPrice.Unavailable;
            return new NexusIapPrice
            {
                Amount = FakePriceValue,
                Currency = FakeCurrency ?? "USD",
                Display = FakePrice ?? "",
            };
        }

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