from typing import List

from sqlalchemy import ForeignKey, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship

from ttfrog.db.base import BaseObject, EnumField, StatsEnum
from ttfrog.db.schema.classes import CharacterClass
from ttfrog.db.schema.constants import DamageType, InventoryType
from ttfrog.db.schema.modifiers import ModifierMixin

__all__ = [
    "ItemType",
    "ItemProperty",
    "Rarity",
    "RechargeTime",
    "Cost",
    "BaseItem",
    "BaseSpell",
    "Armor",
    "Shield",
    "Weapon",
]


ITEM_TYPES = [
    "ITEM",
    "SPELL",
    "SCROLL",
    "WEAPON",
    "ARMOR",
    "SHIELD",
    "CONTAINER",
    "SPELLBOOK",
]

RECHARGE_TIMES = [
    "short rest",
    "long rest",
    "dawn",
]


COST_TYPES = ["Action", "Bonus Action", "Reaction"]

RARITY = ["Common", "Uncommon", "Rare", "Very Rare", "Legendary", "Artifact"]

ItemType = EnumField("ItemType", ((k, k) for k in ITEM_TYPES))
Rarity = EnumField("Rarity", ((k, k) for k in RARITY))
RechargeTime = EnumField("RechargeTime", ((k.replace(" ", "_").upper(), k) for k in RECHARGE_TIMES))
Cost = EnumField("Cost", ((k, k) for k in COST_TYPES))


def item_property_creator(fields):
    if isinstance(fields, list):
        for f in fields:
            yield f
    elif isinstance(fields, ItemProperty):
        return fields
    return ItemProperty(**fields)


class BaseItem(BaseObject, ModifierMixin):
    __tablename__ = "item_prototype"
    __table_args__ = (UniqueConstraint("item_type", "name", "source"),)
    __inventory_item_class__ = "Item"
    __mapper_args__ = {"polymorphic_identity": ItemType.ITEM, "polymorphic_on": "item_type"}

    id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(collation="NOCASE"), nullable=False)
    description: Mapped[str] = mapped_column(String, nullable=True, default=None)

    item_type: Mapped[ItemType] = mapped_column(default=ItemType.ITEM, nullable=False)
    rarity: Mapped[Rarity] = mapped_column(default=Rarity.Common, nullable=False)
    requires_attunement: Mapped[bool] = mapped_column(nullable=False, default=False)
    attunement_restrictions: Mapped[str] = mapped_column(nullable=False, default="")

    consumable: Mapped[bool] = mapped_column(default=False)
    count: Mapped[int] = mapped_column(nullable=False, default=1)

    charges: Mapped[int] = mapped_column(nullable=True, info={"min": 0}, default=None)
    recharge_time: Mapped[RechargeTime] = mapped_column(default=RechargeTime.LONG_REST)
    recharge_amount: Mapped[str] = mapped_column(String(collation="NOCASE"), default="1")

    _class_restrictions: Mapped[int] = mapped_column(ForeignKey("character_class.id"), nullable=True, default=None)
    class_restrictions: Mapped["CharacterClass"] = relationship(init=False)

    properties: Mapped[List["ItemProperty"]] = relationship(
        uselist=True, cascade="all,delete,delete-orphan", lazy="immediate", default_factory=lambda: []
    )

    # if this item is a container, set the inventory type
    inventory_type: Mapped[InventoryType] = mapped_column(nullable=True, default=None)

    # the source publication name
    source: Mapped[str] = mapped_column(default="", nullable=False)

    @property
    def has_charges(self):
        return self.charges is not None

    def __repr__(self):
        return f"{self.__class__.__name__}(id={self.id}, name={self.name})"


class BaseSpell(BaseItem):
    __tablename__ = "spell_prototype"
    __inventory_item_class__ = "Spell"
    __mapper_args__ = {"polymorphic_identity": ItemType.SPELL}
    id: Mapped[int] = mapped_column(ForeignKey("item_prototype.id"), primary_key=True, init=False)
    item_type: Mapped[ItemType] = ItemType.SPELL

    school: Mapped[str] = mapped_column(default="", nullable=False)
    target_range: Mapped[int] = mapped_column(nullable=False, info={"min": 0}, default=0)
    target_shape: Mapped[str] = mapped_column(nullable=True, default=None)
    target_size: Mapped[str] = mapped_column(nullable=True, default=None)
    time: Mapped[str] = mapped_column(default="", nullable=False)
    duration: Mapped[str] = mapped_column(default="", nullable=False)
    level: Mapped[int] = mapped_column(nullable=False, info={"min": 0, "max": 9}, default=0)
    components: Mapped[str] = mapped_column(default="", nullable=False)
    concentration: Mapped[bool] = mapped_column(default=False)

    # XXX some spells do multiple types (e.g. Meteor Swarm). Do we model that here or just in desc?
    damage_die: Mapped[str] = mapped_column(nullable=True, default=None)
    damage_type: Mapped[DamageType] = mapped_column(nullable=True, default=None)
    saving_throw: Mapped[StatsEnum] = mapped_column(nullable=True, default=None)


class Weapon(BaseItem):
    __tablename__ = "weapon"
    __mapper_args__ = {"polymorphic_identity": ItemType.WEAPON}
    id: Mapped[int] = mapped_column(ForeignKey("item_prototype.id"), primary_key=True, init=False)
    item_type: Mapped[ItemType] = ItemType.WEAPON

    damage_die: Mapped[str] = mapped_column(nullable=False, default="1d6")
    damage_die_two_handed: Mapped[str] = mapped_column(nullable=True, default=None)
    damage_type: Mapped[DamageType] = mapped_column(nullable=False, default=DamageType.slashing)
    attack_range: Mapped[int] = mapped_column(nullable=False, info={"min": 0}, default=0)
    attack_range_long: Mapped[int] = mapped_column(nullable=True, info={"min": 0}, default=None)
    reach: Mapped[int] = mapped_column(nullable=False, info={"min": 0}, default=0)
    targets: Mapped[int] = mapped_column(nullable=False, info={"min": 1}, default=1)
    martial: Mapped[bool] = mapped_column(default=False)
    melee: Mapped[bool] = mapped_column(default=False)
    ammunition: Mapped[bool] = mapped_column(default=False)
    finesse: Mapped[bool] = mapped_column(default=False)
    heavy: Mapped[bool] = mapped_column(default=False)
    light: Mapped[bool] = mapped_column(default=False)
    loading: Mapped[bool] = mapped_column(default=False)
    thrown: Mapped[bool] = mapped_column(default=False)
    two_handed: Mapped[bool] = mapped_column(default=False)
    versatile: Mapped[bool] = mapped_column(default=False)
    silvered: Mapped[bool] = mapped_column(default=False)
    adamantine: Mapped[bool] = mapped_column(default=False)
    magical: Mapped[bool] = mapped_column(default=False)

    @property
    def ranged(self):
        return self.attack_range > 0


class Shield(BaseItem):
    __tablename__ = "shield"
    __mapper_args__ = {"polymorphic_identity": ItemType.SHIELD}
    id: Mapped[int] = mapped_column(ForeignKey("item_prototype.id"), primary_key=True, init=False)
    item_type: Mapped[ItemType] = ItemType.SHIELD


class Armor(BaseItem):
    __tablename__ = "armor"
    __mapper_args__ = {"polymorphic_identity": ItemType.ARMOR}
    id: Mapped[int] = mapped_column(ForeignKey("item_prototype.id"), primary_key=True, init=False)
    item_type: Mapped[ItemType] = ItemType.ARMOR
    armor_class: Mapped[int] = mapped_column(nullable=False, info={"min": 0}, default=10)


class ItemProperty(BaseObject):
    __tablename__ = "item_property"
    id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(collation="NOCASE"), nullable=False, unique=True)
    description: Mapped[str] = mapped_column(String, nullable=True, default=None)
    charge_cost: Mapped[int] = mapped_column(nullable=True, info={"min": 1}, default=None)
    item_prototype_id: Mapped[int] = mapped_column(ForeignKey("item_prototype.id"), default=0)

    # action/reaction/bonus
    # modifiers?
