class UserAccess(models.Model):

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=get_rule_ct_filter)
    access_rule = GenericForeignKey('content_type', 'object_id')
    character = models.ForeignKey(EVECharacter, on_delete=models.CASCADE)

    def __unicode__(self):
        output = '%s access by rule for %s applying to %s' % (self.user, self.access_rule, self.character)
        return output.encode('utf-8')

    class Meta:
        permissions = (("site_access", "User has access to site."), ("manage_access", "User can manage site access."), ("audit_access", "User can view access granted per rule."))
        verbose_name_plural = "User access"

    def set_rule(self, object):
        if ContentType.objects.get_for_model(object) in RULE_CONTENT_TYPES:
            self.object_id = object.pk
            self.access_rule = object
        else:
            raise TypeError("Access rule must be of type %s" % RULE_CONTENT_TYPES)

    def get_rule(self):
        return self.access_rule

    def verify(self):
        self.save() class CharacterAccessRule(BaseAccessRule):

    character = models.OneToOneField(EVECharacter, models.CASCADE)
    access = GenericRelation('access.UserAccess')
    auto_group = GenericRelation('groupmanagement.AutoGroup')

    def __unicode__(self):
        output = 'character %s' % self.character
        return output.encode('utf-8')

    def generate_useraccess(self):
        ct = ContentType.objects.get_for_model(self)
        logger.debug("Assigning UserAccess by CharacterAccess rule %s" % self)
        characters = EVECharacter.objects.filter(id=self.character.id)
        useraccess = self.access.all()
        self._validate_rule(characters, useraccess) class CorpAccessRule(BaseAccessRule):

    corp = models.OneToOneField(EVECorporation, models.CASCADE)
    access = GenericRelation('access.UserAccess')
    auto_group = GenericRelation('groupmanagement.AutoGroup')

    def __unicode__(self):
        output = 'corp %s' % self.corp
        return output.encode('utf-8')

    def generate_useraccess(self):
        ct = ContentType.objects.get_for_model(self)
        logger.debug("Assigning UserAccess by CorpAccess rule %s" % self)
        characters = EVECharacter.objects.filter(corp_id=self.corp.id)
        useraccess = self.access.all()
        self._validate_rule(characters, useraccess) class AllianceAccessRule(BaseAccessRule):

    alliance = models.OneToOneField(EVEAlliance, models.CASCADE)
    access = GenericRelation('access.UserAccess')
    auto_group = GenericRelation('groupmanagement.AutoGroup')

    def __unicode__(self):
        output = 'alliance %s' % self.alliance
        return output.encode('utf-8')

    def generate_useraccess(self):
        ct = ContentType.objects.get_for_model(self)
        logger.debug("Assigning UserAccess by AllianceAccess rule %s" % self)
        characters = EVECharacter.objects.filter(alliance_id=self.alliance.id)
        useraccess = self.access.all()
        self._validate_rule(characters, useraccess) class StandingAccessRule(models.Model):
    standings = models.OneToOneField(EVEContactSet, models.CASCADE)

    def __unicode__(self):
        return "%s" % self.standings

    def generate_contactaccess(self):
        for contact in self.standings.contacts.all():
            if ContactAccess.objects.filter(contact=contact).filter(standing_access=self).exists() is False:
                logger.info("Creating ContactAccess for %s from %s" % (contact, self))
                ca = ContactAccess(contact=contact, standing_access=self)
                ca.save()
        for ca in self.contactaccess_set.all():
            if ca.contact in self.standings.contacts.all() is False:
                logger.info("Deleting %s from %s as the contact no longer exists" % (ca, self))

    def generate_useraccess(self):
        for ca in self.contactaccess_set.all():
            ca.generate_useraccess() class ContactAccess(BaseAccessRule):
    contact = models.ForeignKey(EVEContact, on_delete=models.CASCADE)
    standing_access = models.ForeignKey(StandingAccessRule, on_delete=models.CASCADE)
    access = GenericRelation('access.UserAccess')

    attrs = ['id', 'corp_id', 'alliance_id', 'faction_id']

    class Meta:
        unique_together = (('contact', 'standing_access'),)

    def __unicode__(self):
        return "%s from API" % self.contact

    def check_if_applies_to_character(self, char):
        for attr in self.attrs:
            if getattr(char, attr) == self.contact.object_id:
                logger.debug("%s applies to %s by attribute %s" % (self, char, attr))
                return True
        logger.debug("%s does not apply to %s" % (self, char))
        return False

    def __get_applicable_characters(self):
        chars = set([])
        for attr in self.attrs:
            kwargs = {attr:self.contact.object_id}
            for char in EVECharacter.objects.filter(**kwargs):
                chars.add(char)
        logger.debug("%s applies to %s characters" % (self, len(chars)))
        return chars

    def generate_useraccess(self):
        ct = ContentType.objects.get_for_model(self)
        logger.debug("Assigning UserAccess by ContactAccess %s from %s" % (self, self.standing_access))
        useraccess = self.access.all()
        characters = self.__get_applicable_characters()
        self._validate_rule(characters, useraccess) class EVECharacter(models.Model):

    id = models.PositiveIntegerField(primary_key=True)
    name = models.CharField(max_length=254, blank=True)
    corp_id = models.PositiveIntegerField(null=True, blank=True)
    corp_name = models.CharField(max_length=254, null=True, blank=True)
    alliance_id = models.PositiveIntegerField(null=True, blank=True)
    alliance_name = models.CharField(max_length=254, null=True, blank=True)
    faction_id = models.PositiveIntegerField(null=True, blank=True)
    faction_name = models.CharField(max_length=254, null=True, blank=True)
    user = models.ForeignKey('authentication.User', null=True, on_delete=models.SET_NULL, blank=True, related_name='characters')

    objects = EVECharacterManager()

    def __unicode__(self):
        if self.name:
            return self.name.encode('utf-8')
        else:
            logger.warn("Character name missing in character model for id %s - needs an update - returning id as __unicode__" % self.id)
            return str(self.id).encode('utf-8')

    def update(self, char_info=None):
        logger.debug("Initiating update for character model with id %s" % str(self.id))
        if not char_info:
            logger.debug("Not passed API result. Grabbing from evelink")
            api = evelink.eve.EVE()
            result = api.affiliations_for_characters(self.id).result
            if not self.id in result:
                logger.error("API result does not contain this character id %s. Aborting update." % self.id)
                return False
            else:
                char_info = result[self.id]
        logger.debug("Retrieved affiliations for character id %s: %s" % (self.id, char_info))
        if (not 'name' in char_info) or (not 'id' in char_info) or (not 'corp' in char_info):
            logger.error("Passed char_info missing required fields for character id %s. Aborting update." % self.id)
            return False
        if not char_info['name']:
            logger.error("Received empty response from evelink for character id %s - likely bad character id. Aborting update." % self.id)
            return False
        if self.id != char_info['id']:
            logger.error("Received api result for different character id %s, refusing to update character id %s" % (char_info['id'], self.id))
            return False
        update_fields = []
        if self.name != char_info['name']:
            self.name = char_info['name']
            update_fields.append('name')
        if self.corp_id != char_info['corp']['id']:
            self.corp_id = char_info['corp']['id']
            update_fields.append('corp_id')
        if self.corp_name != char_info['corp']['name']:
            self.corp_name = char_info['corp']['name']
            update_fields.append('corp_name')
        if 'faction' in char_info:
            if self.faction_id != char_info['faction']['id']:
                self.faction_id = char_info['faction']['id']
                update_fields.append('faction_id')
            if self.faction_name != char_info['faction']['name']:
                self.faction_name = char_info['faction']['name']
                update_fields.append('faction_name')
        else:
            logger.debug("No faction data found for character id %s. Blanking" % self.id)
            if self.faction_id:
                self.faction_id = None
                update_fields.append('faction_id')
            if self.faction_name:
                self.faction_name = None
                update_fields.append('faction_name')
        if 'alliance' in char_info:
            if self.alliance_id != char_info['alliance']['id']:
                self.alliance_id = char_info['alliance']['id']
                update_fields.append('alliance_id')
            if self.alliance_name != char_info['alliance']['name']:
                self.alliance_name = char_info['alliance']['name']
                update_fields.append('alliance_name')
        else:
            logger.debug("No alliance data found for character id %s. Blanking." % self.id)
            if self.alliance_id:
                self.alliance_id = None
                update_fields.append('alliance_id')
            if self.alliance_name:
                self.alliance_name = None
                update_fields.append('alliance_name')
        if update_fields:
            logger.info("Finished updating character id %s from api. Changed: %s" % (self.id, update_fields))
            self.save(update_fields=update_fields)
        if self.corp_id == EVECharacterManager.DOOMHEIM_CORP_ID:
            logger.info("%s has been biomassed. Deleting model" % self)
            self.delete()
        return True

    def get_possible_users(self):
        from authentication.models import User
        users = set([])
        if User.objects.filter(main_character_id=self.id).exists():
            logger.debug("A user account exists with main_character_id %s" % self.id)
            users.add(User.objects.get(main_character_id=self.id))
        for api in self.apis.filter(is_valid=True).filter(owner__isnull=False):
            logger.debug("%s containing this character has an owner" % api)
            users.add(api.owner)
        logger.debug("Found %s possible owners for %s" % (len(users), self))
        return list(users)

    def assign_user(self):
        users = self.get_possible_users()
        if len(users) == 1:
            logger.debug("%s is not contested and has one possible user" % self)
            if self.user != users[0]:
                logger.debug("Changing assigned user of %s from %s to %s" % (self, self.user, users[0]))
                self.user = users[0]
                self.save(update_fields=['user'])
            else:
                logger.debug("%s has correct user assigned" % self)
        else:
            if len(users) > 1:
                logger.debug("%s is in a contested state with %s possible users: %s" % (self, len(users), users))
            else:
                logger.debug("No possible users found for %s" % self)
            if self.user:
                self.user = None
                self.save(update_fields=['user']) class EVECorporation(models.Model):

    id = models.PositiveIntegerField(primary_key=True)
    name = models.CharField(max_length=254, null=True, blank=True)
    alliance_id = models.PositiveIntegerField(null=True, blank=True)
    alliance_name = models.CharField(max_length=254, null=True, blank=True)
    members = models.PositiveIntegerField(null=True, blank=True)
    ticker = models.CharField(max_length=254, null=True, blank=True)

    objects = EVECorporationManager()

    def __unicode__(self):
        if self.name:
            return self.name.encode('utf-8')
        else:
            logger.warn("Corp name missing in corp model for id %s - needs an update - returning id as __unicode__" % self.id)
            return str(self.id).encode('utf-8')

    def update(self, result=None):
        logger.debug("Updating corp info for corp id %s" % self.id)
        if not result:
            a = evelink.api.API()
            api = evelink.corp.Corp(a)
            try:
                result = api.corporation_sheet(corp_id=self.id).result
            except evelink.api.APIError as e:
                if int(e.code) == 523:
                    logger.info("%s has closed. Deleting model" % self)
                    self.delete()
                else:
                    logger.exception("Error occured retrieving corporation sheet for id %s - aborting update." % self.id, exc_info=True)
                    return False
            except:
                logger.exception("Error occured retrieving corporation sheet for id %s - aborting update." % self.id, exc_info=True)
                return False
        if (not 'name' in result) or (not 'alliance' in result) or (not 'members' in result) or (not 'ticker' in result):
            logger.error("Passed corp result missing required fields for corp id %s. Aborting update." % self.id)
            return False
        if self.id != result['id']:
            logger.error("Received api result for different corp id %s, refusing to update corp id %s" % (result['id'], self.id))
            return False
        update_fields = []
        logger.debug("Got corporation sheet from api for corp id %s: name %s ticker %s members %s" % (result['id'], result['name'], result['ticker'], result['members']['current']))
        if self.name != result['name']:
            self.name = result['name']
            update_fields.append('name')
        if self.alliance_id != result['alliance']['id']:
            self.alliance_id = result['alliance']['id']
            update_fields.append('alliance_id')
        if self.alliance_name != result['alliance']['name']:
            self.alliance_name = result['alliance']['name']
            update_fields.append('alliance_name')
        if self.members != result['members']['current']:
            self.members = result['members']['current']
            update_fields.append('members')
        if self.ticker != result['ticker']:
            self.ticker = result['ticker']
            update_fields.append('ticker')
        logger.info("Finished updating corp info for id %s from api. Changed: %s" % (self.id, update_fields))
        if update_fields:
            self.save(update_fields=update_fields)
        return True class EVEAlliance(models.Model):

    id = models.PositiveIntegerField(primary_key=True)
    name = models.CharField(max_length=254, null=True, blank=True)
    ticker = models.CharField(max_length=254, null=True, blank=True)

    objects = EVEAllianceManager()

    def __unicode__(self):
        if self.name:
            return self.name.encode('utf-8')
        else:
            logger.warn("Alliance name missing in alliance models for id %s - needs an update - returning id as __unicode__" % self.id)
            return str(self.id).encode('utf-8')

    def update(self, alliance_info=None):
        logger.debug("Updating alliance info for alliance id %s" % self.id)
        if not alliance_info:
            logger.debug("Not passed API result. Grabbing from manager")
            alliance_info = EVEAlliance.objects.get_info(self.id)
        if alliance_info['corporationsCount'] == 0:
            logger.info("%s has closed. Deleting model" % self)
            self.delete()
        update_fields=[]
        if self.name != alliance_info['name']:
            self.name = alliance_info['name']
            update_fields.append('name')
        if self.ticker != alliance_info['shortName']:
            self.ticker = alliance_info['shortName']
            update_fields.append('ticker')
        logger.info("Finished updating alliance info for id %s from api. Changed: %s" % (self.id, update_fields))
        if update_fields:
            self.save(update_fields=update_fields)
        return True def pre_save_evecharacter(sender, instance, *args, **kwargs):
def character_changed_user_api_verified_check(sender, character, *args, **kwargs):
def post_save_evecharacter_dispatcher(sender, instance, update_fields=None, *args, **kwargs):
def post_save_eveapikeypair(sender, instance, update_fields=[], *args, **kwargs):
def post_delete_user(sender, instance, *args, **kwargs):
def m2m_changed_eveapikeypair_characters(sender, instance, action, model, pk_set, *args, **kwargs):
def post_delete_evecharacter(sender, instance, *args, **kwargs):
def pre_delete_eveapikeypair(sender, instance, *args, **kwargs):
def post_save_evecorporation(sender, instance, update_fields=[], *args, **kwargs):
def post_save_evealliance(sender, instance, update_fields=[], *args, **kwargs):
def post_save_evecharacter(sender, instance, update_fields=[], *args, **kwargs):
def post_save_evecontactset(sender, instance, update_fields=[], *args, **kwargs): class User(AbstractBaseUser, PermissionsMixin):
    main_character_id = models.PositiveIntegerField(primary_key=True)
    email = models.EmailField(max_length=255, null=True, blank=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)

    USERNAME_FIELD = 'main_character_id'
    REQUIRED_FIELDS = []

    objects = UserManager()

    class Meta:
        verbose_name = 'user'
        verbose_name_plural = 'users'

    @property
    def main_character(self):
        if self.characters.filter(id=self.main_character_id).exists():
            return self.characters.get(id=self.main_character_id)
        elif EVECharacter.objects.filter(id=self.main_character_id).exists():
            logger.warn("User with main character ID %s has detached main character model" % self.get_full_name())
            return EVECharacter.objects.get(id=self.main_character_id)
        else:
            return None

    def get_full_name(self):
        return str(self.main_character_id)
    def get_short_name(self):
        if self.main_character:
            return str(self.main_character)
        else:
            return self.get_full_name()

    def __unicode__(self):
        if self.main_character:
            return str(self.main_character).encode('utf-8')
        else:
            logger.debug("Missing character model for user with main character id %s, returning id as __unicode__." % self.main_character_id)
            return self.get_short_name().encode('utf-8')
from celery import shared_task
from django.contrib.contenttypes.models import ContentType
from access.models import UserAccess, CharacterAccessRule, CorpAccessRule, AllianceAccessRule, StandingAccessRule, ContactAccess
from eveonline.models import EVECharacter, EVECorporation, EVEAlliance
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.contrib.auth.models import Permission
from eveonline.signals import character_changed_corp, character_changed_alliance, character_changed_user, character_blind_save
from .signals import user_loses_access, user_gains_access
from authentication.models import User
from authentication.signals import user_created
import logging
from __future__ import absolute_import


logger = logging.getLogger(__name__)

@shared_task
def assess_access(user):
    logger.debug("Assess access rights for user %s" % user)
    perm, c = Permission.objects.get_or_create(content_type=ContentType.objects.get_for_model(UserAccess), codename='site_access')
    if user.useraccess_set.all().exists():
        logger.debug("User %s gets access because existing access rules %s" % (user, user.useraccess_set.all()))
        if not perm in user.user_permissions.all():
            logger.info("Assigning access permission to user %s: %s" % (user, perm))
            user.user_permissions.add(perm)
            user_gains_access.send(sender=UserAccess, user=user)
    else:
        logger.debug("User %s does not get access because no existing access rules." % user)
        if perm in user.user_permissions.all():
            logger.info("Removing access permission from user %s: %s" % (user, perm))
            user.user_permissions.remove(perm)
            user_loses_access.send(sender=UserAccess, user=user)

@shared_task
def assign_access(user):
    logger.debug("Assigning access for user %s" % user)
    chars = user.characters.all()
    for char in chars:
        logger.debug("Assigning acces for user %s based on character %s" % (user, char))
        if CharacterAccessRule.objects.filter(character=char).exists():
            ca = CharacterAccessRule.objects.get(character=char)
            logger.debug("CharacterAccess rule exists for character %s. Assigning." % char)
            ca.generate_useraccess()
        if EVECorporation.objects.filter(id=char.corp_id).exists():
            logger.debug("Corp model exists for character %s corp %s" % (char, char.corp_name))
            corp = EVECorporation.objects.get(id=char.corp_id)
            if CorpAccessRule.objects.filter(corp=corp).exists():
                ca = CorpAccessRule.objects.get(corp=corp)
                if not ca.access.filter(user=user).filter(character=char).exists():
                    logger.info("CorpAccess rule %s applies to user %s by character %s. Assigning UserAccess model." % (ca, user, char))
                    ua = UserAccess(user=user, character=char)
                    ua.set_rule(ca)
                    ua.save()
        if EVEAlliance.objects.filter(id=char.alliance_id).exists():
            logger.debug("Alliance model exists for character %s alliance %s" % (char, char.alliance_name))
            alliance = EVEAlliance.objects.get(id=char.alliance_id)
            if AllianceAccessRule.objects.filter(alliance=alliance).exists():
                aa = AllianceAccessRule.objects.get(alliance=alliance)
                if not aa.access.filter(user=user).filter(character=char).exists():
                    logger.info("AllianceAccess rule %s applies to user %s by character %s. Assigning UserAccess model." % (aa, user, char))
                    ua = UserAccess(user=user, character=char)
                    ua.set_rule(aa)
                    ua.save()
    logger.debug("Finished generating useraccess models for user %s" % user)

@receiver(post_delete, sender=UserAccess)
def post_delete_useraccess(sender, instance, *args, **kwargs):
    if instance.user:
        logger.debug("Received post_delete signal from UserAccess models %s. Triggering assessment of user %s access rights." % (instance, instance.user))
        assess_access(instance.user)
    else:
        logger.debug("Received post_delete signal from UserAccess model %s. No affiliated user found. Ignoring." % instance)

@receiver(character_changed_corp, sender=EVECharacter)
def useraccess_check_on_character_changed_corp(sender, character, *args, **kwargs):
    ct = ContentType.objects.get_for_model(CorpAccessRule)
    ua = UserAccess.objects.filter(content_type=ct).filter(character=character)
    logger.debug("Received character_changed_corp signal from character %s - checking %s useraccess models." % (character, len(ua)))
    for u in ua:
        if u.access_rule.corp.id != character.corp_id:
            logger.info("Character %s new corp does not apply to corpaccess rule %s - deleting useraccess." % (character, u.access_rule))
            u.delete()
    if character.user:
        logger.debug("Assigning new access rights to character %s owner %s" % (character, character.user))
        assign_access(character.user)
    else:
        logger.debug("Character %s does not have a user. Not assiging access." % character)

@receiver(character_changed_alliance, sender=EVECharacter)
def useraccess_check_on_character_changed_alliance(sender, character, *args, **kwargs):
    ct = ContentType.objects.get_for_model(AllianceAccessRule)
    ua = UserAccess.objects.filter(content_type=ct).filter(character=character)
    logger.debug("Received character_changed_alliance signal from character %s - checking %s useraccess models." % (character, len(ua)))
    for u in ua:
        if u.acces_rule.alliance.id != character.alliance_id:
            logger.info("Character %s new alliance does not apply to allianceaccess rule %s - deleting useraccess." % (character, u.access_rule))
    if character.user:
        logger.debug("Assigning new access rights to character %s owner %s" % (character, character.user))
        assign_access(character.user)
    else:
        logger.debug("Character %s does not have a user. Not assiging access." % character)

@receiver(character_changed_user, sender=EVECharacter)
def useraccess_check_on_character_changed_user(sender, character, *args, **kwargs):
    ua = UserAccess.objects.filter(character=character)
    logger.debug("Received character_changed_user signal from character %s - checking %s useraccess models." % (character, len(ua)))
    for u in ua:
        if u.user != character.user:
            logger.info("Character %s new user does not match useraccess %s - deleting." % (character, u))
            u.delete()
    if character.user:
        logger.debug("Assigning new access rights to character %s owner %s" % (character, character.user))
        assign_access(character.user)
    else:
        logger.debug("Character %s does not have a user. Not assiging access." % character)


@receiver(character_blind_save, sender=EVECharacter)
def useraccess_check_on_character_blind_save(sender, character, *args, **kwargs):
    logger.debug("Received character_blind_save signal from character %s - checking all useraccess." % character)
    ua = UserAccess.objects.filter(character=character)
    if character.user:
        for u in ua:

            if u.content_type == ContentType.objects.get_for_model(CharacterAccessRule):