from ttfrog.db.schema import prototypes
from ttfrog.db.schema.inventory import InventoryType


def test_spell_inventory(db, carl):
    with db.transaction():
        fireball = prototypes.BaseSpell(name="Fireball", level=3, concentration=False)
        db.add_or_update([fireball, carl])

        assert fireball not in carl.spells
        assert carl.spells.learn(fireball)
        db.add_or_update(carl)

        assert fireball in carl.spells
        assert fireball in carl.spells.known
        assert fireball in carl.spells.available
        assert fireball not in carl.spells.prepared

        prestidigitation = prototypes.BaseSpell(name="Prestidigitation", level=0, concentration=False)
        wish = prototypes.BaseSpell(name="Wish", level=9, concentration=False)

        spellbook = prototypes.BaseItem(name="Spell Book", inventory_type=InventoryType.SPELL)
        db.add_or_update([wish, spellbook])

        assert wish not in carl.spells

        grimoire = carl.equipment.add(spellbook)
        db.add_or_update(carl)
        grimoire.inventory.add(wish)
        grimoire.inventory.add(prestidigitation)
        db.add_or_update(carl)

        assert wish in carl.spells.available
        assert wish not in carl.spells.known

        assert prestidigitation in carl.spells.available
        assert prestidigitation not in carl.spells.known

        assert carl.spells.get(wish)
        assert carl.spells.get(prestidigitation)


def test_equipment_inventory(db, carl):
    with db.transaction():
        # trigger the creation of inventory mappings
        db.add_or_update(carl)

        # create some items
        ten_foot_pole = prototypes.BaseItem(name="10ft. Pole", item_type=prototypes.ItemType.ITEM, consumable=False)
        db.add_or_update(ten_foot_pole)

        # add the pole to carl's equipment, and the spell to his spell list.
        assert carl.equipment.add(ten_foot_pole)

        # can't mix and match inventory item types
        assert not carl.spells.learn(ten_foot_pole)

        # add two more 10 foot poles. You can never have too many.
        assert carl.equipment.add(ten_foot_pole)
        assert carl.equipment.add(ten_foot_pole)
        db.add_or_update(carl)

        all_carls_poles = carl.equipment.get_all(ten_foot_pole)
        assert len(all_carls_poles) == 3

        # check the "contains" logic
        assert ten_foot_pole in carl.equipment
        assert ten_foot_pole not in carl.spells

        pole_one = all_carls_poles[0]

        # equip one pole
        assert pole_one.equip()

        # can't equip it twice
        assert not pole_one.equip()

        # not consumable or attunable
        assert not pole_one.consume()
        assert not pole_one.attune()

        assert pole_one.unequip()

        # can't unequip the unequipped ones
        assert not all_carls_poles[1].unequip()
        assert not all_carls_poles[2].unequip()

        # drop one pole
        assert carl.equipment.remove(pole_one)
        assert ten_foot_pole in carl.equipment
        return

        # drop the remaining poles
        assert carl.equipment.remove(all_carls_poles[1])
        assert carl.equipment.remove(all_carls_poles[2])
        assert ten_foot_pole not in carl.equipment

        # can't drop what you don't have
        assert not carl.equipment.remove(pole_one)


def test_inventory_bundles(db, carl):
    with db.transaction():
        arrows = prototypes.BaseItem(name="Arrows", item_type=prototypes.ItemType.ITEM, consumable=True, count=20)
        db.add_or_update([carl, arrows])
        quiver = carl.equipment.add(arrows)
        db.add_or_update([carl, quiver])

        assert quiver.container == carl.equipment

        # full quiver
        assert arrows in carl.equipment
        assert quiver.count == 20

        # use one
        assert quiver.consume(1) == 19
        assert quiver.count == 19

        # cannot use more than you have
        assert not quiver.consume(20)

        # cannot use a negative amount
        assert not quiver.consume(-1)

        # consume all remaining arrows
        assert quiver.consume(19) == 0
        assert arrows not in carl.equipment


def test_spell_slots(db, carl, wizard):
    with db.transaction():
        prestidigitation = prototypes.BaseSpell(name="Prestidigitation", level=0, concentration=False)
        fireball = prototypes.BaseSpell(name="Fireball", level=3, concentration=False)
        db.add_or_update([carl, prestidigitation, fireball])

        carl.spells.learn(prestidigitation)
        carl.spells.learn(fireball)
        db.add_or_update(carl)

        # verify carl has the spell slots granted by wizard at 1st level
        assert len(carl.spell_slots) == 2
        assert carl.spell_slots[0].spell_level == 1
        assert carl.spell_slots[1].spell_level == 1

        # carl knows the spells but hasn't prepared them
        assert prestidigitation in carl.spells
        assert fireball in carl.spells
        assert prestidigitation not in carl.spells.prepared
        assert fireball not in carl.spells.prepared

        # prepare the cantrip
        assert carl.spells.prepare(prestidigitation)
        assert carl.spells.cast(prestidigitation)

        # can't prepare a 3rd level spell if you don't have 3rd level slots
        assert carl.spellcaster_level == 1
        assert not carl.spells.prepare(fireball)

        # make carl a 5th level wizard so he gets a 3rd level spell slot
        carl.level_up(wizard, num_levels=4)
        assert carl.level == 5
        assert carl.spellcaster_level == 3

        # cast fireball until he's out of 3rd level slots
        assert not carl.spells.cast(fireball)
        assert carl.spells.prepare(fireball)
        assert carl.spells.cast(fireball)
        assert carl.spells.cast(fireball)
        assert not carl.spells.cast(fireball)

        # level up to 7th level, gaining 1 4th level slot and 1 more 3rd level slot
        carl.add_class(wizard)
        carl.level_up(wizard, num_levels=2)
        assert carl.spellcaster_level == 4
        assert len(carl.spell_slots_available[4]) == 1
        assert len(carl.spell_slots_available[3]) == 1

        # cast at 4th level
        assert carl.spells.cast(fireball, level=4)
        assert not carl.spells.cast(fireball, level=4)

        # use the last 3rd level slot
        assert carl.spells.cast(fireball)
        assert not carl.spells.cast(fireball)

        # unprepare it
        assert carl.spells.unprepare(fireball)
        assert not carl.spells.unprepare(fireball)


def test_containers(db, carl):
    with db.transaction():
        ten_foot_pole = prototypes.BaseItem(name="10ft. Pole")
        coil_of_rope = prototypes.BaseItem(name="50 ft. of Rope", consumable=True, count=50)
        bag_of_holding = prototypes.BaseItem(name="Bag of Holding", inventory_type=InventoryType.EQUIPMENT)
        db.add_or_update([ten_foot_pole, coil_of_rope, bag_of_holding])

        pole = carl.equipment.add(ten_foot_pole)
        rope = carl.equipment.add(coil_of_rope)
        bag = carl.equipment.add(bag_of_holding)
        db.add_or_update(carl)

        # verify the bag of holding's inventory is created automatically.
        assert bag.inventory_type is not None
        assert bag.inventory is not None

        # the existing instances are found using the get() method
        assert carl.equipment.get(bag_of_holding) == bag
        assert carl.equipment.get(ten_foot_pole) == pole
        assert carl.equipment.get(coil_of_rope) == rope

        # backreferences are populated correctly
        assert pole.container == carl.equipment

        # add some items to the bag of holding
        assert pole.move_to(bag)
        assert rope.move_to(bag)

        assert pole.container.id == bag.inventory.id
        assert pole.container == bag.inventory
        assert pole in bag.inventory
        assert pole in bag

        assert pole not in carl.equipment.contents
        assert pole.container == bag.inventory

        pole_from_bag = bag.inventory.get(ten_foot_pole)
        rope_from_bag = bag.inventory.get(coil_of_rope)

        assert pole_from_bag.prototype == ten_foot_pole
        assert pole_from_bag in bag

        bag_inventory_size = 2  # one pole, one rope
        equipment_size = 1  # one bag

        # one bag, one pole, one rope
        total_inventory_size = bag_inventory_size + equipment_size

        assert len(list(bag.inventory.contents)) == bag_inventory_size
        assert len(list(carl.equipment.contents)) == equipment_size
        assert len(list(carl.equipment.all_contents)) == total_inventory_size

        # nested containers!
        assert pole_from_bag in carl.equipment
        assert rope_from_bag in carl.equipment

        # test equality of mappings
        carls_bag = carl.equipment.get(bag_of_holding)
        carls_pole = carl.equipment.get(ten_foot_pole)
        carls_rope = carl.equipment.get(coil_of_rope)
        assert carls_pole == pole_from_bag
        assert carls_rope == rope_from_bag

        # use some rope
        assert carls_rope.consume(10)
        assert carls_rope.count == 40

        # move the rope out of the bag of holding, but not the pole
        assert carls_rope in carls_bag
        assert carls_rope.move_to(carl.equipment)
        assert carls_rope not in carls_bag

        assert carls_pole in carls_bag

        # get the db record anew, in case the in-memory representation isn't
        # what's recorded in the database. Then make sure we didn't break
        # anything by asserting we still only have 40ft of rope.
        carl = db.Character.filter_by(name="carl").one()
        assert carls_rope in carl.equipment
        assert carls_rope not in carl.equipment.get(bag_of_holding)
        assert carls_rope.count == 40

        # old references are still valid
        assert rope_from_bag == carls_rope

        # use the rest of the rope
        assert carls_rope.consume(40) == 0
        assert rope_from_bag not in carl.equipment
        assert rope_from_bag not in carl.equipment.get(bag_of_holding)
