import base64

from flask_sqlalchemy import SQLAlchemy
from ..models import db, BaseModel


class CharacterClass(BaseModel):
    __tablename__ = 'character_class'

    name = db.Column(db.String(50), unique=True, nullable=False)
    spellcaster = db.Column(db.String(10))
    bucket = db.Column(db.String(50))
    frequency_modifier = db.Column(db.Integer, default=1)

    prime_requisite = db.Column(db.String(3))
    hit_die_size = db.Column(db.Integer)
    maximum_level = db.Column(db.Integer)

    armour_modifier = db.Column(db.Integer)
    melee_light = db.Column(db.Integer)
    melee_medium = db.Column(db.Integer)
    melee_heavy = db.Column(db.Integer)
    ranged_light = db.Column(db.Integer)
    ranged_heavy = db.Column(db.Integer)

    def __repr__(self):
        return '<CharacterClass: {0}>'.format(self.name)

    @property
    def is_divine_spellcaster(self):
        return self.spellcaster == 'Divine'

    @property
    def is_arcane_spellcaster(self):
        return self.spellcaster == 'Arcane'

class ClassLevelProgression(BaseModel):
    __tablename__ = 'class_progression'

    level = db.Column(db.Integer)
    attack_throw = db.Column(db.Integer)

    save_petrification_paralysis = db.Column(db.Integer)
    save_poison_death = db.Column(db.Integer)
    save_blast_breath = db.Column(db.Integer)
    save_staffs_wands = db.Column(db.Integer)
    save_spells = db.Column(db.Integer)

    spellslots1 = db.Column(db.Integer)
    spellslots2 = db.Column(db.Integer)
    spellslots3 = db.Column(db.Integer)
    spellslots4 = db.Column(db.Integer)
    spellslots5 = db.Column(db.Integer)
    spellslots6 = db.Column(db.Integer)

    guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
    guild = db.relationship('CharacterClass', backref=db.backref('progressions', lazy=True))

    def spell_slots(self, level=None):
        if level > self.guild.maximum_level:
            level = self.guild.maximum_level

        spellslots = [
            self.spellslots1,
            self.spellslots2,
            self.spellslots3,
            self.spellslots4,
            self.spellslots5,
            self.spellslots6
        ]

        if level:
            return spellslots[level - 1]

        return spellslots

    def __repr__(self):
        return '<LevelProgression: {0} {1}>'.format(self.level, self.guild.name)


class Spell(BaseModel):
    __tablename__ = 'spells'

    name = db.Column(db.String(50), unique=True, nullable=False)
    range = db.Column(db.String(50))
    duration = db.Column(db.String(50))
    arcane = db.Column(db.Integer, nullable=False)
    divine = db.Column(db.Integer, nullable=False)
    description = db.Column(db.Text(1000), nullable=False)

    def __repr__(self):
        return '<Spell: {0} ({1})>'.format(self.name, self.school)

    def level_for(self, npc):
        return self.arcane if npc.is_arcane_spellcaster else self.divine

    @property
    def school(self):
        if self.is_arcane and self.is_divine:
            return 'Multi'
        if self.is_arcane:
            return 'Arcane'
        if self.is_divine:
            return 'Divine'

    @property
    def is_divine(self):
        return bool(self.divine and self.divine > 0)

    @property
    def is_arcane(self):
        return bool(self.arcane and self.arcane > 0)

    @property
    def dom_id(self):
        return self.name.lower().replace("*", "").replace("'", "").replace(",", "").replace(" ", "_")

    @property
    def roll20_format(self):
        spell_dict = {
            'id': self.id,
            'name': self.name,
            'range': self.range,
            'duration': self.duration,
            'divine': self.divine,
            'is_divine': self.is_divine,
            'arcane': self.arcane,
            'is_arcane': self.is_arcane,
            'description': base64.b64encode(self.description.encode('ascii')).decode('ascii')
        }
        return spell_dict 

class EquipmentArmour(BaseModel):
    __tablename__ = 'eq_armour'

    name = db.Column(db.String(50), unique=True, nullable=False)
    gp_value = db.Column(db.Integer, nullable=False)
    ac_mod = db.Column(db.Integer, nullable=False)

    def __repr__(self):
        return '<EquipmentArmour: {0}>'.format(self.name)

class EquipmentRangedWeapon(BaseModel):
    __tablename__ = 'eq_ranged_wep'

    name = db.Column(db.String(50), unique=True, nullable=False)
    bucket = db.Column(db.String(20), nullable=False)
    gp_value = db.Column(db.Integer, nullable=False)
    damage_die = db.Column(db.String(10), nullable=False)

    def __repr__(self):
        return '<EquipmentRangedWeapon: {0}>'.format(self.name)

class EquipmentMeleeWeapon(BaseModel):
    __tablename__ = 'eq_melee_wep'

    name = db.Column(db.String(50), unique=False, nullable=False)
    bucket = db.Column(db.String(20), nullable=False)
    gp_value = db.Column(db.Integer, nullable=False)
    damage_die = db.Column(db.String(10), nullable=False)
    two_handed = db.Column(db.Boolean, nullable=False)

    def __repr__(self):
        return '<EquipmentMeleeWeapon: {0}>'.format(self.name)

class CharacterNPC(BaseModel):
    __tablename__ = 'npcs'

    name = db.Column(db.String(50), unique=False, nullable=True)
    level = db.Column(db.Integer, nullable=False)
    alignment = db.Column(db.String(20), unique=False, nullable=False)
    hit_points = db.Column(db.Integer, nullable=False)

    strength = db.Column(db.Integer, nullable=False)
    intelligence = db.Column(db.Integer, nullable=False)
    wisdom = db.Column(db.Integer, nullable=False)
    dexterity = db.Column(db.Integer, nullable=False)
    constitution = db.Column(db.Integer, nullable=False)
    charisma = db.Column(db.Integer, nullable=False)

    spells = db.Column(db.String(200))

    guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
    guild = db.relationship('CharacterClass', backref=db.backref('npcs', lazy=True))

    melee_id = db.Column(db.Integer, db.ForeignKey('eq_melee_wep.id'), nullable=False)
    melee = db.relationship('EquipmentMeleeWeapon')

    ranged_id = db.Column(db.Integer, db.ForeignKey('eq_ranged_wep.id'), nullable=True)
    ranged = db.relationship('EquipmentRangedWeapon')

    armour_id = db.Column(db.Integer, db.ForeignKey('eq_armour.id'), nullable=False)
    armour = db.relationship('EquipmentArmour')

    def __repr__(self):
        return '<CharacterNPC: {0}>'.format(self.name)

    @staticmethod
    def calculate_attr_mod(attr):
        mod = 0
        if attr <= 3:
            mod  = -3
        elif attr >= 4 and attr <= 5:
            mod = -2
        elif attr >= 6 and attr <= 8:
            mod = -1
        elif attr >= 9 and attr <= 12:
            mod = 0
        elif attr >= 13 and attr <= 15:
            mod = 1
        elif attr >= 16 and attr <= 16:
            mod = 2
        elif attr >= 18:
            mod = 3
        return mod

    def spell_slots(self, level=None):
        if level > self.guild.maximum_level:
            level = self.guild.maximum_level
        return self.current_progression.spell_slots(level)

    def spells_known(self, level=None):
        if level:
            if level > self.guild.maximum_level:
                level = self.guild.maximum_level
            # Return for a specific level
            if self.is_arcane_spellcaster:
                return (self.spell_slots(level) + self.wis_mod)
            return self.spell_slots(level)

        # Return for all levels
        if self.is_arcane_spellcaster:
            return [self.spell_slots(x) + self.wis_mod for x in range(1,6)]
        return [self.spell_slots(x) for x in range(1,6)]

    def spell_list(self, level=None):
        spell_ids = self.spells.split(',')
        spells = Spell.query.filter(Spell.id.in_(spell_ids)).all()

        spells_by_level = {}
        for spell in spells:
            spells_by_level.setdefault(spell.level_for(self), []).append(spell)

        return spells_by_level

    @property
    def roll20_format(self):
        npc_dict = {
            'name': self.name,
            'level': self.level,
            'hp': self.hit_points,
            'guild': self.guild.name,
            'alignment': self.alignment,
            "attack_throw": self.attack_throw,
            'attributes': {
                'str': self.strength,
                'int': self.intelligence,
                'wis': self.wisdom,
                'dex': self.dexterity,
                'con': self.constitution,
                'chr': self.charisma
            },
            'attribute_mods': {
                'str': self.str_mod,
                'int': self.int_mod,
                'wis': self.wis_mod,
                'dex': self.dex_mod,
                'con': self.con_mod,
                'chr': self.chr_mod
            },
            "saves": {
                "pp": self.save_pp,
                "pd": self.save_pd,
                "bb": self.save_bb,
                "sw": self.save_sw,
                "sp": self.save_sp
            },
            'armour': {
                'name': self.armour.name,
                'value': self.armour.gp_value,
                'ac_mod': self.armour.ac_mod
            },
            'melee': {
                'name': self.melee.name,
                'value': self.melee.gp_value,
                'damage': self.melee.damage_die,
                'throw_mod': 0,
                'two_handed': self.melee.two_handed
            }
        }

        if self.guild.is_divine_spellcaster or self.guild.is_arcane_spellcaster:
            npc_dict['spells'] = []
            spell_list = self.spell_list()
            for level in spell_list:
                for spell in spell_list[level]:
                    npc_dict['spells'].append(spell.roll20_format)

        if self.ranged:
            npc_dict['ranged'] = {
                'name': self.ranged.name,
                'value': self.ranged.gp_value,
                'damage': self.ranged.damage_die,
                'throw_mod': 0
            }

        return npc_dict 

    @property
    def str(self):
        return self.strength

    @property
    def int(self):
        return self.intelligence

    @property
    def wis(self):
        return self.wisdom

    @property
    def dex(self):
        return self.dexterity

    @property
    def con(self):
        return self.constitution

    @property
    def chr(self):
        return self.charisma

    @property
    def str_mod(self):
        return self.calculate_attr_mod(self.strength)

    @property
    def int_mod(self):
        return self.calculate_attr_mod(self.intelligence) 

    @property
    def wis_mod(self):
        return self.calculate_attr_mod(self.wisdom)

    @property
    def dex_mod(self):
        return self.calculate_attr_mod(self.dexterity)

    @property
    def con_mod(self):
        return self.calculate_attr_mod(self.constitution)

    @property
    def chr_mod(self):
        return self.calculate_attr_mod(self.charisma)

    @property
    def current_progression(self):
        return ClassLevelProgression.query.filter_by(guild_id=self.guild.id, level=self.level).first()

    @property
    def attack_throw(self):
        return self.current_progression.attack_throw

    @property
    def save_pp(self):
        return self.current_progression.save_petrification_paralysis

    @property
    def save_pd(self):
        return self.current_progression.save_petrification_paralysis

    @property
    def save_bb(self):
        return self.current_progression.save_blast_breath

    @property
    def save_sw(self):
        return self.current_progression.save_staffs_wands

    @property
    def save_sp(self):
        return self.current_progression.save_spells

    @property
    def hp(self):
        return self.hit_points

    @property
    def is_divine_spellcaster(self):
        return self.guild.is_divine_spellcaster

    @property
    def is_arcane_spellcaster(self):
        return self.guild.is_arcane_spellcaster

    def update(self, kwargs):
        # Allows us to update like a dict, used for inserting attributes zip
        self.__dict__.update(kwargs)

admin_models = [CharacterClass, ClassLevelProgression, EquipmentArmour, EquipmentRangedWeapon, EquipmentMeleeWeapon, CharacterNPC, Spell]
