from typing import Dict
from sqlalchemy import  Integer, String, Float, ForeignKey, Boolean
from sqlalchemy.orm import relationship, Mapped, mapped_column
from database_service import Base

# Models
class Condition(Base):
    """
    Model for conditions, applicable to creatures

    Source: DND5e(2014) PHB, p.290-292
    """
    __tablename__ = "conditions"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(20), nullable=False, primary_key=True)
    desc: Mapped[str] = mapped_column(String, nullable=False)


class Alignment(Base):
    """
    Alignments
    """
    __tablename__ = "alignments"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(50), nullable=False, primary_key=True)    
    abbreviation: Mapped[str] = mapped_column(String(50), nullable=False, primary_key=True)    
    desc: Mapped[str] = mapped_column(String, nullable=False)


class Monster(Base):
    """
    Model for defining a monster, official or custom
    Source: DND5e(2014) MM
    """
    __tablename__ = "monsters"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(50), nullable=False, primary_key=True)
    desc: Mapped[str] = mapped_column(String, nullable=True)

    type_id: Mapped[int] = mapped_column(Integer, ForeignKey('creature_types.id'))
    type: Mapped["CreatureType"] = relationship(
        "CreatureType",
    )
    
    sub_type_id: Mapped[int] = mapped_column(Integer, ForeignKey('creature_sub_types.id'))
    sub_type: Mapped["CreatureSubType"] = relationship(
        "CreatureSubType",
    )

    size_id: Mapped[int] = mapped_column(Integer, ForeignKey('creature_sizes.id'))
    size: Mapped["CreatureSize"] = relationship(
        "CreatureSize",
    )

    alignment: Mapped[int] = mapped_column(Integer, ForeignKey('alignments.id'))
    alignment: Mapped["Alignment"] = relationship(
        "Alignment",
    )


    armor_class: Mapped[int] = mapped_column(Integer)
    armors: Mapped["MonsterArmor"] = relationship('MonsterArmor', back_populates="monster") 
    speed: Mapped[str]

    # ability scores
    strength: Mapped[int] = mapped_column(Integer)
    # storage is cheap, computation is expensive, store this
    strength_mod: Mapped[int] = mapped_column(Integer)

    dexterity: Mapped[int] = mapped_column(Integer)
    dexterity_mod: Mapped[int] = mapped_column(Integer)

    # will frequently be filtered by these
    challenge_rating: Mapped[Float] = mapped_column(Float, index=True)
    xp_rating: Mapped[int] = mapped_column(Integer, index=True)

    hit_dice: Mapped[str] = mapped_column(String)
    hit_point_formula: Mapped[str] = mapped_column(String)
    hit_point_average: Mapped[int] = mapped_column(Integer)

    # NOTE: if need actual combat automation, make the following fields beloe as junction tables
    # TODO: make this into junction table, so we can take list and then modify and format each element
    # as desired
    actions: Mapped[str] = mapped_column(String, nullable=False)
    # TODO: ditto
    legendary_actions: Mapped[str] = mapped_column(String, nullable=True)
    # TODO: ditto
    special_abilities: Mapped[str] = mapped_column(String)

    senses: Mapped[str]

    damage_resistances: Mapped[str] = mapped_column(String, nullable=True)
    damage_immunities: Mapped[str] = mapped_column(String, nullable=True)
    condition_immunities: Mapped[str] = mapped_column(String, nullable=True)
    speeds: Mapped[str] = mapped_column(String, nullable=False)
    saving_throws: Mapped[str] = mapped_column(String)
    # END NOTE
    
    languages: Mapped["MonsterLanguage"] = relationship("MonsterLanguage", back_populates="monster")
    spells: Mapped["MonsterSpell"] = relationship("MonsterSpell", back_populates="monster")



    def __init__(self, *args, **kwargs):
        super().__init__()
        self.calculate_and_store_ability_scores({
            'strength': args.strength,
            'dexterity': args.dexterity,
            'constitution': args.constitution,
            'intelligence': args.intelligence,
            'wisdom': args.wisdom,
            'charisma': args.charisma
        })


    def calculate_and_store_ability_scores(self, ability_scores: Dict[str, int]):
        for ability_name, score in ability_scores:
            # 1. dynamically assign attribute by string literal
            setattr(self, ability_name, score)
            # 2. call calculate_ability_modifier
            modifier = Monster.calculate_ability_modifier()
            # 3. store that dynamically as well - assume column naming schema is unchanged
            setattr(self, f"{ability_name}_mod", modifier)


    @staticmethod
    def calculate_ability_modifier(ability_score:int):
        return (ability_score - 10) // 2 

class Armor(Base):
    __tablename__ = "armors"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)    
    name: Mapped[str] = mapped_column(String(100), primary_key=True)
    weight: Mapped[int] = mapped_column(Integer, nullable=False)
    description: Mapped[str] = mapped_column(String, nullable=True)
    strength_requirement: Mapped[int] = mapped_column(Integer, default=0)
    armor_class: Mapped[int] = mapped_column(Integer, nullable=False)
    max_dex_bonus: Mapped[int] = mapped_column(Integer, nullable=False)
    stealth_disadvantage: Mapped[Boolean] = mapped_column(Boolean, nullable=False)
    ac_enchantment: Mapped[int] = mapped_column(Integer, default=0)
    special_effects: Mapped[str] = mapped_column(String)
    requires_attunement: Mapped[Boolean] = mapped_column(Boolean, nullable=False)


class MonsterArmor(Base):
    """
    Junction table for Monster *-* Armor
    """
    __tablename__ = 'monster_armor'
    
    monster_id: Mapped[int] = mapped_column(Integer, ForeignKey('monsters.id'), primary_key=True, unique=True)
    armor_id: Mapped[int] = mapped_column(Integer, ForeignKey('armors.id'), primary_key=True, unique=True)
    
    # Relationship to Monster and Armor
    monster: Mapped["Monster"] = relationship("Monster", back_populates="armors")
    armor: Mapped["Armor"] = relationship("Armor")


class CreatureType(Base):
    """
    Creature type

    Example: "humanoid"
    """
    __tablename__ = "creature_types"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(50), nullable=False, primary_key=True)    

class CreatureSubType(Base):
    """
    Creature sub-type

    Example: "goblinoid"
    """
    __tablename__ = "creature_sub_types"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(50), nullable=False, primary_key=True)    

class CreatureSize(Base):
    """
    Creature size, as defined in DND5e(2014) PHB p.191
    """
    __tablename__ = "creature_sizes"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(20), nullable=False, primary_key=True)
    # NOTE: what about Gargantuan? It is a range?
    space = mapped_column(Float, nullable=False)
    


class ActionAttack(Base):
    __tablename__ = "actions"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(100), nullable=False, primary_key=True)


class Language(Base):
    __tablename__ = "languages"
    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(50), nullable=False, primary_key=True)


class Spell(Base):
    __tablename__ = "spells"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(300), nullable=False, primary_key=True)
    
    # NOTE: we handle "at higher levels", by adding a new entry with a different level
    level = mapped_column(Integer, primary_key=True)
    magic_school_id: Mapped[int] = mapped_column(Integer, ForeignKey('magic_schools.id'))
    magic_school: Mapped["MagicSchool"] = relationship(
        "MagicSchool",
    )
    
    classes: Mapped["SpellClass"] = relationship("SpellClass", back_populates="spell")

    # NOTE: this should be computed and converted to combat rounds, hours, 
    # days, etc, as appropriate, per context
    duration_minutes: Mapped[int] = mapped_column(Integer)
    casting_time_id: Mapped[str] = mapped_column(String, nullable=False)
    range: Mapped[int] = mapped_column(Integer)
    area_of_effect_id: Mapped[int] = mapped_column(Integer, ForeignKey('area_of_effects.id'))
    area_of_effect: Mapped["AreaOfEffect"] = relationship(
        "AreaOfEffect",
    )
    concentration: Mapped[bool] = mapped_column(Boolean)

    components: Mapped[str] = mapped_column(String)


class CharacterClass(Base):
    """
    TODO: finish this up; as minimal columns set as possible
    """
    __tablename__ = "character_classes"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)


class SpellClass(Base):
    """
    Junction Table for Spell *-* CharacterClass
    """
    __tablename__ = "spell_to_class"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)

    class_id: Mapped[int] = mapped_column(Integer, ForeignKey('character_classes.id'), primary_key=True)
    character_class: Mapped["CharacterClass"] = relationship("CharacterClass")
    
    spell_id: Mapped[int] = mapped_column(Integer, ForeignKey('spells.id'), primary_key=True)
    spell: Mapped["Spell"] = relationship("Spell", back_populates="classes")


class MonsterSpell(Base):
    """
    Junciton table for spell *-* mosnter
    """
    __tablename__ = "spell_to_monster"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)

    monster_id: Mapped[int] = mapped_column(Integer, ForeignKey('monsters.id'),primary_key=True, unique=True)
    monster: Mapped["Monster"] = relationship('Monster', back_populates="spells")

    spell_id: Mapped[int] = mapped_column(Integer, ForeignKey('spells.id'), primary_key=True, unique=True)
    spell: Mapped["Spell"] = relationship("Spell")


class MonsterLanguage(Base):
    """
    Junction table for Language *-* Monster
    """
    __tablename__ = "monster_language"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)

    monster_id: Mapped[int] = mapped_column(Integer, ForeignKey('monsters.id'), primary_key=True, unique=True)
    monster: Mapped["Monster"] = relationship('Monster')

    language_id: Mapped[int] = mapped_column(Integer, ForeignKey('languages.id'), primary_key=True, unique=True)
    language: Mapped["Language"] = relationship('Language')



class AreaOfEffect(Base):
    __tablename__ = "area_of_effects"
    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(20), nullable=False, primary_key=True)
    desc: Mapped[str] = mapped_column(String, nullable=False)


class MagicSchool(Base):
    __tablename__ = "magic_schools"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, unique=True)
    name: Mapped[str] = mapped_column(String(20), nullable=False, primary_key=True)
    desc: Mapped[str] = mapped_column(String, nullable=False)
    

