using NUnit.Framework;
using UnityEngine;
using YachtDice.Dice;
using YachtDice.Economy;
using YachtDice.Inventory;
using YachtDice.Player;
using YachtDice.Shop;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Runtime;

namespace YachtDice.Tests
{
    public sealed class ShopModelTests
    {
        private CurrencyBank bank;
        private ModifierRegistry registry;
        private InventoryModel inventory;
        private DiceCollection diceCollection;
        private ShopModel shop;

        [SetUp]
        public void SetUp()
        {
            var go = new GameObject("Bank");
            bank = go.AddComponent<CurrencyBank>();
            bank.SetBalance(500);

            registry = new ModifierRegistry(5);
            inventory = new InventoryModel(registry);
            diceCollection = new DiceCollection();
            shop = new ShopModel(bank, inventory, diceCollection);
        }

        [TearDown]
        public void TearDown()
        {
            foreach (var go in Object.FindObjectsByType<CurrencyBank>(FindObjectsSortMode.None))
                Object.DestroyImmediate(go.gameObject);
        }

        private ModifierDefinition CreateDef(string id = "test",
            bool hasLimitedUses = false, int maxUses = 0,
            int shopPrice = 100, int sellPrice = 50)
        {
            return ModifierDefinition.CreateForTest(id, null,
                hasLimitedUses: hasLimitedUses, maxUses: maxUses,
                shopPrice: shopPrice, sellPrice: sellPrice);
        }

        [Test]
        public void TryPurchase_SucceedsWithSufficientCurrency()
        {
            var mod = CreateDef("test", shopPrice: 100);

            bool result = shop.TryPurchase(mod);

            Assert.IsTrue(result);
            Assert.AreEqual(400, bank.Balance);
            Assert.AreEqual(1, inventory.OwnedModifiers.Count);
        }

        [Test]
        public void TryPurchase_FailsWhenBroke()
        {
            bank.SetBalance(10);
            var mod = CreateDef("test", shopPrice: 100);

            bool result = shop.TryPurchase(mod);

            Assert.IsFalse(result);
            Assert.AreEqual(10, bank.Balance);
            Assert.AreEqual(0, inventory.OwnedModifiers.Count);
        }

        [Test]
        public void TryPurchase_PermanentCannotBeBoughtTwice()
        {
            var mod = CreateDef("perm", shopPrice: 100);

            shop.TryPurchase(mod);
            bool secondResult = shop.TryPurchase(mod);

            Assert.IsFalse(secondResult);
            Assert.AreEqual(400, bank.Balance);
            Assert.AreEqual(1, inventory.OwnedModifiers.Count);
        }

        [Test]
        public void TryPurchase_LimitedCanBeReBought()
        {
            var mod = CreateDef("limited", hasLimitedUses: true, maxUses: 3, shopPrice: 100);

            shop.TryPurchase(mod);
            bool secondResult = shop.TryPurchase(mod);

            Assert.IsTrue(secondResult);
            Assert.AreEqual(300, bank.Balance);
            Assert.AreEqual(2, inventory.OwnedModifiers.Count);
        }

        [Test]
        public void TryPurchase_FiresPurchaseEvent()
        {
            IShopItem purchased = null;
            shop.OnItemPurchased += item => purchased = item;

            var mod = CreateDef("test", shopPrice: 100);

            shop.TryPurchase(mod);

            Assert.IsNotNull(purchased);
            Assert.AreEqual("test", purchased.Id);
        }

        [Test]
        public void GetItemState_Available_WhenCanAfford()
        {
            var mod = CreateDef("test", shopPrice: 100);

            Assert.AreEqual(ShopItemState.Available, shop.GetItemState(mod));
        }

        [Test]
        public void GetItemState_TooExpensive_WhenCannotAfford()
        {
            bank.SetBalance(10);
            var mod = CreateDef("test", shopPrice: 100);

            Assert.AreEqual(ShopItemState.TooExpensive, shop.GetItemState(mod));
        }

        [Test]
        public void GetItemState_Owned_WhenPermanentPurchased()
        {
            var mod = CreateDef("perm", shopPrice: 100);

            shop.TryPurchase(mod);

            Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(mod));
        }

        [Test]
        public void TryPurchase_DieItem_AddsToDiceCollection()
        {
            var die = DieDefinitionSO.CreateForTest<StandardDieSO>("test_die", shopPrice: 100);

            bool result = shop.TryPurchase(die);

            Assert.IsTrue(result);
            Assert.AreEqual(400, bank.Balance);
            Assert.AreEqual(1, diceCollection.OwnedDice.Count);
        }

        [Test]
        public void TryPurchase_DieItem_CannotBeBoughtTwice()
        {
            var die = DieDefinitionSO.CreateForTest<StandardDieSO>("unique_die", shopPrice: 100);

            shop.TryPurchase(die);
            bool secondResult = shop.TryPurchase(die);

            Assert.IsFalse(secondResult);
            Assert.AreEqual(400, bank.Balance);
            Assert.AreEqual(1, diceCollection.OwnedDice.Count);
        }

        [Test]
        public void GetItemState_Die_Owned_AfterPurchase()
        {
            var die = DieDefinitionSO.CreateForTest<StandardDieSO>("die1", shopPrice: 50);

            shop.TryPurchase(die);

            Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(die));
        }
    }
}
