from django.db import models

import uuid
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    """
    Custom user model that extends AbstractUser, adding a UUID field
    for unique identification and linking to other tables.
    """
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)

    def __str__(self):
        return self.username



def check_requirements(character, requirements):
    for requirement in requirements:
        char_prop = getattr(character, requirement['property'])
        #print(char_prop, requirement['value'], requirement['condition'])
        if requirement['condition'] == "==":
            #print("==")
            if requirement['value'] != char_prop:
                return False
        if requirement['condition'] == "!=":
            #print("!=")
            if requirement['value'] == char_prop:
                return False
        if requirement['condition'] == "<=":
            #print("<=")
            if requirement['value'] < char_prop:
                return False
        if requirement['condition'] == ">=":
            #print(">=")
            if requirement['value'] > char_prop:
                return False
    return True

def get_operations_for_hp_max(character, attr):
    ops = []
    for feature in character.hp_features:
        if check_requirements(character, feature['feature_requirements']):
            feature_ops = feature['feature_data'].get('operations', [])
            for op in feature_ops:
                if op['attr'] == attr:
                    ops.append(op)
    return ops

def get_operations_for_attr(character, attr):
    ops = []
    for feature in character.features.all():
        feature_ops = feature.feature_data.get('operations', [])
        if check_requirements(character, feature.feature_requirements):
            for op in feature_ops:
                if op['attr'] == attr:
                    ops.append(op)
        
    return ops

def apply_operations(base, operations, character):
    """Apply a list of operations (add, multiply, set, etc) in order."""
    value = base
    for op in operations:
        if op['operation'] == 'add':
            if isinstance(op['value'], int):
                value += op['value']
            if isinstance(op['value'], str):
                value += getattr(character, op['value'])
        elif op['operation'] == 'subtract':
            if isinstance(op['value'], int):
                value -= op['value']
            if isinstance(op['value'], str):
                value -= getattr(character, op['value'])
        elif op['operation'] == 'multiply':
            if isinstance(op['value'], int):
                value *= op['value']
            if isinstance(op['value'], str):
                value *= getattr(character, op['value'])
        elif op['operation'] == 'divide':
            if isinstance(op['value'], int):
                value /= op['value']
            if isinstance(op['value'], str):
                value /= getattr(character, op['value'])
        elif op['operation'] == 'set':
            if isinstance(op['value'], int):
                value = op['value']
            if isinstance(op['value'], str):
                value = getattr(character, op['value'])
    return value


# Character model based on character_template.json
class Character(models.Model):
    objects = models.Manager()
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    owner = models.ForeignKey('CustomUser', on_delete=models.CASCADE, related_name='characters')
    name = models.CharField(max_length=100)
    level = models.IntegerField(default=0)
    race = models.ForeignKey('Package', related_name='race_characters', null=True, blank=True, on_delete=models.SET_NULL)
    subrace = models.ForeignKey('Package', related_name='subrace_characters', null=True, blank=True, on_delete=models.SET_NULL)
    char_class = models.ForeignKey('Package', related_name='class_characters', null=True, blank=True, on_delete=models.SET_NULL)
    subclass = models.ForeignKey('Package', related_name='subclass_characters', null=True, blank=True, on_delete=models.SET_NULL)
    background = models.ForeignKey('Package', related_name='background_characters', null=True, blank=True, on_delete=models.SET_NULL)
    alignment = models.CharField(max_length=50, blank=True)
    size = models.CharField(max_length=20, blank=True)
    age = models.IntegerField(default=0)
    gender = models.CharField(max_length=30, blank=True)
    height = models.CharField(max_length=20, blank=True)
    weight = models.IntegerField(default=0)
    deity = models.CharField(max_length=100, blank=True)
    
    # Relationships to packages and features
    packages = models.ManyToManyField('Package', blank=True, related_name='characters')
    features = models.ManyToManyField('Feature', blank=True, related_name='characters')

    # Base and current stats
    strength_base = models.IntegerField(default=0)
    dexterity_base = models.IntegerField(default=0)
    constitution_base = models.IntegerField(default=0)
    intelligence_base = models.IntegerField(default=0)
    wisdom_base = models.IntegerField(default=0)
    charisma_base = models.IntegerField(default=0)
    armor_base = models.IntegerField(default=0)

    inspiration = models.BooleanField(default=False)
    experience = models.IntegerField(default=0)

    # Proficiencies and languages
    proficiencies_armor = models.JSONField(default=list, blank=True)
    proficiencies_weapons = models.JSONField(default=list, blank=True)
    proficiencies_tools = models.JSONField(default=list, blank=True)
    languages = models.JSONField(default=list, blank=True)

    # Saving throws
    strength_save_prof = models.BooleanField(default=False)
    dexterity_save_prof = models.BooleanField(default=False)
    constitution_save_prof = models.BooleanField(default=False)
    intelligence_save_prof = models.BooleanField(default=False)
    wisdom_save_prof = models.BooleanField(default=False)
    charisma_save_prof = models.BooleanField(default=False)

    # HP and combat
    hp = models.IntegerField(default=0)
    #hp_max = models.IntegerField(default=0)
    hp_temp = models.IntegerField(default=0)
    hit_die = models.CharField(max_length=20, blank=True)
    #initiative = models.IntegerField(default=0)

    # Death saves
    deathsaves_successes = models.IntegerField(default=0)
    deathsaves_failures = models.IntegerField(default=0)

    # Resistances
    fire_resistance = models.BooleanField(default=False)
    poison_resistance = models.BooleanField(default=False)
    psychic_resistance = models.BooleanField(default=False)
    cold_resistance = models.BooleanField(default=False)
    thunder_resistance = models.BooleanField(default=False)
    acid_resistance = models.BooleanField(default=False)
    force_resistance = models.BooleanField(default=False)
    radiant_resistance = models.BooleanField(default=False)
    necrotic_resistance = models.BooleanField(default=False)
    bludgeoning_resistance = models.BooleanField(default=False)
    piercing_resistance = models.BooleanField(default=False)
    slashing_resistance = models.BooleanField(default=False)
    immunities = models.JSONField(default=list, blank=True)
    vulnerabilities = models.JSONField(default=list, blank=True)

    # Movement & vision
    speed_base = models.IntegerField(default=0)
    speed_type = models.JSONField(default=list, blank=True)
    darkvision = models.IntegerField(default=0)
    blindsight = models.IntegerField(default=0)
    tremorsense = models.IntegerField(default=0)
    truesight = models.IntegerField(default=0)

    # Spellcasting
    spellcasting_ability = models.CharField(max_length=50, blank=True)
    spell_save_dc = models.IntegerField(default=0)
    spell_attack_bonus = models.IntegerField(default=0)
    known_spells = models.JSONField(default=list, blank=True)
    prepared_spells = models.JSONField(default=list, blank=True)
    spell_slots = models.JSONField(default=dict, blank=True)
    cantrips = models.JSONField(default=list, blank=True)
    spellcasting_class = models.CharField(max_length=100, blank=True)

    # Exhaustion, conditions, notes, and attributes
    exhaustion_level = models.IntegerField(default=0)
    conditions_active = models.JSONField(default=list, blank=True)
    cp = models.IntegerField(default=0)
    sp = models.IntegerField(default=0)
    ep = models.IntegerField(default=0)
    gp = models.IntegerField(default=0)
    pp = models.IntegerField(default=0)
    equipment = models.JSONField(default=list, blank=True)
    traits = models.JSONField(default=list, blank=True)
    personality_traits = models.JSONField(default=list, blank=True)
    ideals = models.JSONField(default=list, blank=True)
    bonds = models.JSONField(default=list, blank=True)
    flaws = models.JSONField(default=list, blank=True)
    notes = models.TextField(blank=True)
    hp_features = models.JSONField(default=list, blank=True)
    custom_attributes = models.JSONField(default=dict, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.name} (Level {self.level})"

    @classmethod
    def from_db(cls, db, field_names, values):
        instance = super().from_db(db, field_names, values)
        # Here, do your consolidation logic:
        instance.feature_operations = instance.build_feature_operations_dict()
        return instance

    def build_feature_operations_dict(self):
        feature_dict = {}
        #Build HP
        for feature in self.hp_features:
            feature_requirements = feature['feature_requirements']
            if check_requirements(self, feature_requirements):
                for operation in feature['feature_data'].get('operations', []):
                    if 'requirements' in operation:
                        if check_requirements(self, operation['requirements']):
                            feature_dict.setdefault(operation['attr'], []).append(operation)
                    else:
                        feature_dict.setdefault(operation['attr'], []).append(operation)

        #Build features
        for feature in self.features.all():
            feature_requirements = feature.feature_requirements
            if check_requirements(self, feature_requirements):
                for operation in feature.feature_data.get('operations', []):
                    if 'requirements' in operation:
                        if check_requirements(self, operation['requirements']):
                            feature_dict.setdefault(operation['attr'], []).append(operation)
                    else:
                        feature_dict.setdefault(operation['attr'], []).append(operation)
        
        print(self.packages.all())

        # Build race
        if self.race != None:
            for feature in self.race.features.all():
                feature_requirements = feature.feature_requirements
                if check_requirements(self, feature_requirements):
                    for operation in feature.feature_data.get('operations', []):
                        if 'requirements' in operation:
                            if check_requirements(self, operation['requirements']):
                                feature_dict.setdefault(operation['attr'], []).append(operation)
                        else:
                            feature_dict.setdefault(operation['attr'], []).append(operation)

        for feature in feature_dict:
            print(feature)
        return feature_dict

    def stat_total(self, attr):
        base = getattr(self, f"{attr}_base")
        ops = self.feature_operations.get(attr, [])
        return apply_operations(base, ops, self)
    
    @property
    def proficiency(self):
        lvl = getattr(self, 'level')
        return 2 + (lvl-1) // 4

    @property
    def strength(self):
        return self.stat_total('strength')

    @property
    def strength_modifier(self):
        return (getattr(self, 'strength')-10) // 2

    @property
    def strength_save(self):
        sav = getattr(self, 'strength_modifier')
        if getattr(self, 'strength_save_prof'):
            return getattr(self, 'proficiency') + sav
        return sav

    @property
    def dexterity(self):
        return self.stat_total('dexterity')

    @property
    def dexterity_modifier(self):
        return (getattr(self, 'dexterity')-10) //2

    @property
    def dexterity_save(self):
        sav = getattr(self, 'dexterity_modifier')
        if getattr(self, 'dexterity_save_prof'):
            return getattr(self, 'proficiency') + sav
        return sav

    @property
    def constitution(self):
        return self.stat_total('constitution')

    @property
    def constitution_modifier(self):
        return (getattr(self, 'constitution')-10) //2

    @property
    def constitution_save(self):
        sav = getattr(self, 'constitution_modifier')
        if getattr(self, 'constitution_save_prof'):
            return getattr(self, 'proficiency') + sav
        return sav

    @property
    def intelligence(self):
        return self.stat_total('intelligence')

    @property
    def intelligence_modifier(self):
        return (getattr(self, 'intelligence')-10) //2

    @property
    def intelligence_save(self):
        sav = getattr(self, 'intelligence_modifier')
        if getattr(self, 'intelligence_save_prof'):
            return getattr(self, 'proficiency') + sav
        return sav
    
    @property
    def wisdom(self):
        return self.stat_total('wisdom')

    @property
    def wisdom_modifier(self):
        return (getattr(self, 'wisdom')-10) //2

    @property
    def wisdom_save(self):
        sav = getattr(self, 'wisdom_modifier')
        if getattr(self, 'wisdom_save_prof'):
            return getattr(self, 'proficiency') + sav
        return sav

    @property
    def charisma(self):
        return self.stat_total('charisma')

    @property
    def charisma_modifier(self):
        return (getattr(self, 'charisma')-10) //2

    @property
    def charisma_save(self):
        sav = getattr(self, 'charisma_modifier')
        if getattr(self, 'charisma_save_prof'):
            return getattr(self, 'proficiency') + sav
        return sav

    @property
    def armor_class(self):
        return self.stat_total('armor_class')

    @property
    def hp_max(self):
        con = getattr(self, 'constitution_modifier')
        level = getattr(self, 'level')
        base = con * level
        ops = self.feature_operations.get('hp_max', [])
        print(ops, base)
        bonus = apply_operations(base, ops, self)
        return bonus

    @property
    def initiative(self):
        return 0

    @property
    def athletics(self):
        ops = get_operations_for_attr(self, 'athletics')
        base = getattr(self, 'strength_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus + getattr(self, 'proficiency')

    @property
    def athletics_passive(self):
        return getattr(self, 'athletics') + 10

    @property
    def acrobatics(self):
        ops = get_operations_for_attr(self, 'acrobatics')
        base = getattr(self, 'dexterity_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def acrobatics_passive(self):
        return getattr(self, 'acrobatics') + 10

    @property
    def sleight_of_hand(self):
        ops = get_operations_for_attr(self, 'sleight_of_hand')
        base = getattr(self, 'dexterity_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def sleight_of_hand_passive(self):
        return getattr(self, 'sleight_of_hand') + 10

    @property
    def stealth(self):
        ops = get_operations_for_attr(self, 'stealth')
        base = getattr(self, 'dexterity_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def stealth_passive(self):
        return getattr(self, 'stealth') + 10

    @property
    def arcana(self):
        ops = get_operations_for_attr(self, 'arcana')
        base = getattr(self, 'intelligence_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def arcana_passive(self):
        return getattr(self, 'arcana') + 10

    @property
    def history(self):
        ops = get_operations_for_attr(self, 'history')
        base = getattr(self, 'intelligence_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def history_passive(self):
        return getattr(self, 'history') + 10

    @property
    def investigation(self):
        ops = get_operations_for_attr(self, 'investigation')
        base = getattr(self, 'intelligence_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def investigation_passive(self):
        return getattr(self, 'investigation') + 10

    @property
    def nature(self):
        ops = get_operations_for_attr(self, 'nature')
        base = getattr(self, 'intelligence_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def nature_passive(self):
        return getattr(self, 'nature') + 10
    
    @property
    def religion(self):
        ops = get_operations_for_attr(self, 'religion')
        base = getattr(self, 'intelligence_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def religion_passive(self):
        return getattr(self, 'religion') + 10

    @property
    def animal_handling(self):
        ops = get_operations_for_attr(self, 'animal_handling')
        base = getattr(self, 'wisdom_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def animal_handling_passive(self):
        return getattr(self, 'animal_handling') + 10

    @property
    def insight(self):
        ops = get_operations_for_attr(self, 'insight')
        base = getattr(self, 'wisdom_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def insight_passive(self):
        return getattr(self, 'insight') + 10

    @property
    def medicine(self):
        ops = get_operations_for_attr(self, 'medicine')
        base = getattr(self, 'wisdom_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def medicine_passive(self):
        return getattr(self, 'medicine') + 10

    @property
    def perception(self):
        ops = get_operations_for_attr(self, 'perception')
        base = getattr(self, 'wisdom_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def perception_passive(self):
        return getattr(self, 'perception') + 10

    @property
    def survival(self):
        ops = get_operations_for_attr(self, 'survival')
        base = getattr(self, 'wisdom_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def survival_passive(self):
        return getattr(self, 'survival') + 10

    @property
    def deception(self):
        ops = get_operations_for_attr(self, 'deception')
        base = getattr(self, 'charisma_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def deception_passive(self):
        return getattr(self, 'deception') + 10

    @property
    def intimidation(self):
        ops = get_operations_for_attr(self, 'intimidation')
        base = getattr(self, 'charisma_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def intimidation_passive(self):
        return getattr(self, 'intimidation') + 10

    @property
    def performance(self):
        ops = get_operations_for_attr(self, 'performance')
        base = getattr(self, 'charisma_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def performance_passive(self):
        return getattr(self, 'performance') + 10

    @property
    def persuasion(self):
        ops = get_operations_for_attr(self, 'persuasion')
        base = getattr(self, 'charisma_modifier')
        bonus = apply_operations(0, ops, self)
        return base + bonus

    @property
    def persuasion_passive(self):
        return getattr(self, 'persuasion') + 10
    

# Feature model based on feature_template.json
class Feature(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    feature_name = models.CharField(max_length=200)
    feature_system = models.CharField(max_length=36, blank=True)
    feature_description = models.TextField(blank=True)
    feature_requirements = models.JSONField(default=list, blank=True)
    #feature requirements requires 3 keys, {"property", "value", "condition"}
    feature_data = models.JSONField(default=dict, blank=True)
    #feature_data holds a value of {"operations", "sources"}
    # ----> operations is a list of dictionaries that have to have 6 values
    # ----> {"attr", "value", "operation", "limits", "operation_requirements", "priority"}
    feature_traits = models.ManyToManyField('ObjectTrait', blank=True, related_name='features')

    def __str__(self):
        return self.feature_name

    def to_json(self):
        return {
            'id': str(self.id),
            'feature_name': self.feature_name,
            'feature_description': self.feature_description
        }

# Package <-> Feature through model for priorities
class PackageFeature(models.Model):
    package = models.ForeignKey('Package', on_delete=models.CASCADE)
    feature = models.ForeignKey('Feature', on_delete=models.CASCADE)
    priority = models.IntegerField(default=0)
    requirements_override = models.JSONField(blank=True, null=True)

    class Meta:
        unique_together = ('package', 'feature')
        ordering = ['priority']

    def __str__(self):
        return f"{self.package} - {self.feature} (priority {self.priority})"

# Package model based on package_template.json
class Package(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    package_name = models.CharField(max_length=200)
    package_system = models.CharField(max_length=36, blank=True)
    package_description = models.TextField(blank=True)
    package_type = models.CharField(max_length=100, blank=True)
    package_doc_md = models.TextField(blank=True)
    package_requirements = models.JSONField(default=list, blank=True)
    #feature requirements requires 3 keys, {"property", "value", "condition"}
    package_operations = models.JSONField(default=list, blank=True)
    #feature_data holds a value of {"operations", "sources"}
    # ----> operations is a list of dictionaries that have to have 6 values
    # ----> {"attr", "value", "operation", "limits", "operation_requirements", "priority"}
    features = models.ManyToManyField('Feature', through='PackageFeature', blank=True, related_name='packages')
    package_traits = models.ManyToManyField('ObjectTrait', blank=True, related_name='packages')

    def __str__(self):
        return self.package_name


class Asset(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    asset_name = models.CharField(max_length=256)
    asset_description = models.TextField(blank=True)
    asset_doc_md = models.TextField(blank=True)
    asset_system = models.CharField(max_length=32, blank=True)
    asset_requirements = models.JSONField(default=list, blank=True)

class ObjectTrait(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    trait_name = models.CharField(max_length=64)
    trait_description = models.TextField(blank=True)
    trait_system = models.CharField(max_length=32, blank=True)

    def __str__(self):
        return self.trait_name

    def to_json(self):
        return {
            'id': str(self.id),
            'trait_name': self.trait_name
        }

class Pin(models.Model):
    label = models.CharField(max_length=100)
    url = models.URLField(max_length=300)
    x = models.FloatField(help_text="X position as percentage (0-100)")
    y = models.FloatField(help_text="Y position as percentage (0-100)")
    pin_type = models.CharField(max_length=100, default="general")
    def as_dict(self):
        return {
            "label": self.label,
            "url": self.url,
            "x": self.x,
            "y": self.y,
            "pin_type": self.pin_type,
        }
    def __str__(self):
        return f"{self.label} ({self.x:.2f}%, {self.y:.2f}%)"