"""Notification related models"""

from datetime import datetime, timedelta
import json
import logging
from time import sleep
import yaml

import pytz
import dhooks_lite

from django.db import models
from django.core.validators import MinValueValidator
from django.conf import settings
from django.utils.translation import gettext_lazy as _, gettext
from django.utils import translation

from allianceauth.eveonline.evelinks import dotlan

from esi.models import Token
from multiselectfield import MultiSelectField

from ..app_settings import (
    STRUCTURES_DEFAULT_LANGUAGE,
    STRUCTURES_DEVELOPER_MODE,
    STRUCTURES_MOON_EXTRACTION_TIMERS_ENABLED,
    STRUCTURES_NOTIFICATION_MAX_RETRIES,    
    STRUCTURES_NOTIFICATION_WAIT_SEC,    
    STRUCTURES_REPORT_NPC_ATTACKS,    
    STRUCTURES_TIMERS_ARE_CORP_RESTRICTED,
)
from .. import __title__
from ..managers import EveEntityManager
from ..utils import (
    LoggerAddTag, DATETIME_FORMAT, make_logger_prefix, app_labels
)
from .eveuniverse import EveType, EveSolarSystem, EveMoon, EvePlanet
from .structures import Structure

if 'timerboard' in app_labels():
    from allianceauth.timerboard.models import Timer

logger = LoggerAddTag(logging.getLogger(__name__), __title__)

# Supported languages
LANGUAGES = (
    ('en', _('English')),
    ('de', _('German')),
    ('es', _('Spanish')),
    ('zh-hans', _('Chinese Simplified')),
    ('ru', _('Russian')),
    ('ko', _('Korean')),
)

# Notification types
NTYPE_MOONS_AUTOMATIC_FRACTURE = 401
NTYPE_MOONS_EXTRACTION_CANCELED = 402
NTYPE_MOONS_EXTRACTION_FINISHED = 403
NTYPE_MOONS_EXTRACTION_STARTED = 404
NTYPE_MOONS_LASER_FIRED = 405

NTYPE_STRUCTURE_ANCHORING = 501
NTYPE_STRUCTURE_DESTROYED = 502
NTYPE_STRUCTURE_FUEL_ALERT = 503
NTYPE_STRUCTURE_LOST_ARMOR = 504
NTYPE_STRUCTURE_LOST_SHIELD = 505
NTYPE_STRUCTURE_ONLINE = 506
NTYPE_STRUCTURE_SERVICES_OFFLINE = 507
NTYPE_STRUCTURE_UNANCHORING = 508
NTYPE_STRUCTURE_UNDER_ATTACK = 509
NTYPE_STRUCTURE_WENT_HIGH_POWER = 510
NTYPE_STRUCTURE_WENT_LOW_POWER = 511
NTYPE_STRUCTURE_REINFORCE_CHANGED = 512
NTYPE_OWNERSHIP_TRANSFERRED = 513

NTYPE_ORBITAL_ATTACKED = 601
NTYPE_ORBITAL_REINFORCED = 602

NTYPE_TOWER_ALERT_MSG = 701
NTYPE_TOWER_RESOURCE_ALERT_MSG = 702

NTYPE_SOV_ENTOSIS_CAPTURE_STARTED = 801
NTYPE_SOV_COMMAND_NODE_EVENT_STARTED = 802
NTYPE_SOV_ALL_CLAIM_ACQUIRED_MSG = 803
NTYPE_SOV_STRUCTURE_REINFORCED = 804
NTYPE_SOV_STRUCTURE_DESTROYED = 805

NTYPE_CHOICES = [
    # moon mining
    (NTYPE_MOONS_AUTOMATIC_FRACTURE, 'MoonminingAutomaticFracture'),
    (NTYPE_MOONS_EXTRACTION_CANCELED, 'MoonminingExtractionCancelled'),
    (NTYPE_MOONS_EXTRACTION_FINISHED, 'MoonminingExtractionFinished'),
    (NTYPE_MOONS_EXTRACTION_STARTED, 'MoonminingExtractionStarted'),
    (NTYPE_MOONS_LASER_FIRED, 'MoonminingLaserFired'),

    # upwell structures general
    (NTYPE_OWNERSHIP_TRANSFERRED, 'OwnershipTransferred'),
    (NTYPE_STRUCTURE_ANCHORING, 'StructureAnchoring'),
    (NTYPE_STRUCTURE_DESTROYED, 'StructureDestroyed'),
    (NTYPE_STRUCTURE_FUEL_ALERT, 'StructureFuelAlert'),
    (NTYPE_STRUCTURE_LOST_ARMOR, 'StructureLostArmor'),
    (NTYPE_STRUCTURE_LOST_SHIELD, 'StructureLostShields'),
    (NTYPE_STRUCTURE_ONLINE, 'StructureOnline'),
    (NTYPE_STRUCTURE_SERVICES_OFFLINE, 'StructureServicesOffline'),
    (NTYPE_STRUCTURE_UNANCHORING, 'StructureUnanchoring'),
    (NTYPE_STRUCTURE_UNDER_ATTACK, 'StructureUnderAttack'),
    (NTYPE_STRUCTURE_WENT_HIGH_POWER, 'StructureWentHighPower'),
    (NTYPE_STRUCTURE_WENT_LOW_POWER, 'StructureWentLowPower'),

    # custom offices only
    (NTYPE_ORBITAL_ATTACKED, 'OrbitalAttacked'),
    (NTYPE_ORBITAL_REINFORCED, 'OrbitalReinforced'),

    # starbases only
    (NTYPE_TOWER_ALERT_MSG, 'TowerAlertMsg'),
    (NTYPE_TOWER_RESOURCE_ALERT_MSG, 'TowerResourceAlertMsg'),

    # sov
    (NTYPE_SOV_ENTOSIS_CAPTURE_STARTED, 'EntosisCaptureStarted'),
    (NTYPE_SOV_COMMAND_NODE_EVENT_STARTED, 'SovCommandNodeEventStarted'),
    (NTYPE_SOV_ALL_CLAIM_ACQUIRED_MSG, 'SovAllClaimAquiredMsg'),
    (NTYPE_SOV_STRUCTURE_REINFORCED, 'SovStructureReinforced'),
    (NTYPE_SOV_STRUCTURE_DESTROYED, 'SovStructureDestroyed'),
]

_NTYPE_RELEVANT_FOR_TIMERBOARD = [
    NTYPE_STRUCTURE_LOST_SHIELD,
    NTYPE_STRUCTURE_LOST_ARMOR,
    NTYPE_STRUCTURE_ANCHORING,
    NTYPE_ORBITAL_REINFORCED,
    NTYPE_MOONS_EXTRACTION_STARTED,
    NTYPE_MOONS_EXTRACTION_CANCELED,
    NTYPE_SOV_STRUCTURE_REINFORCED
]

NTYPE_FOR_ALLIANCE_LEVEL = [
    NTYPE_SOV_ENTOSIS_CAPTURE_STARTED,
    NTYPE_SOV_COMMAND_NODE_EVENT_STARTED,
    NTYPE_SOV_ALL_CLAIM_ACQUIRED_MSG,
    NTYPE_SOV_STRUCTURE_REINFORCED,
    NTYPE_SOV_STRUCTURE_DESTROYED
]


def get_default_notification_types():
    """generates a set of all existing notification types as default"""
    return tuple(sorted([str(x[0]) for x in NTYPE_CHOICES]))


class Webhook(models.Model):
    """A destination for forwarding notification alerts"""

    TYPE_DISCORD = 1

    TYPE_CHOICES = [
        (TYPE_DISCORD, _('Discord Webhook')),
    ]

    name = models.CharField(
        max_length=64,
        unique=True,
        help_text='short name to identify this webhook'
    )
    webhook_type = models.IntegerField(
        choices=TYPE_CHOICES,
        default=TYPE_DISCORD,
        help_text='type of this webhook'
    )
    url = models.CharField(
        max_length=255,
        unique=True,
        help_text=(
            'URL of this webhook, e.g. '
            'https://discordapp.com/api/webhooks/123456/abcdef'
        )
    )
    notes = models.TextField(
        null=True,
        default=None,
        blank=True,
        help_text='you can add notes about this webhook here if you want'
    )
    notification_types = MultiSelectField(
        choices=NTYPE_CHOICES,
        default=get_default_notification_types,
        help_text=(
            'only notifications which selected types are sent to this webhook'
        )
    )
    language_code = models.CharField(
        max_length=8, 
        choices=LANGUAGES,
        default=None,
        null=True,
        blank=True,
        verbose_name='language',
        help_text='language of notifications send to this webhook'
    )
    is_active = models.BooleanField(
        default=True,
        help_text='whether notifications are currently sent to this webhook'
    )
    is_default = models.BooleanField(
        default=False,
        help_text=(
            'whether owners have this webhook automatically '
            'pre-set when created'
        )
    )

    def __str__(self):
        return self.name

    def __repr__(self):
        return '{}(id={}, name=\'{}\')'.format(
            self.__class__.__name__,
            self.id,
            self.name
        )

    def send_test_notification(self) -> str:
        """Sends a test notification to this webhook and returns send report"""
        hook = dhooks_lite.Webhook(self.url)
        response = hook.execute(
            _(
                'This is a test notification from %s.\n'
                'The webhook appears to be correctly configured.'
            ) % __title__,
            wait_for_response=True
        )
        if response.status_ok:
            send_report_json = json.dumps(
                response.content, indent=4, sort_keys=True
            )
        else:
            send_report_json = 'HTTP status code {}'.format(
                response.status_code
            )
        return send_report_json


class EveEntity(models.Model):
    """An EVE entity like a character or an alliance"""

    CATEGORY_CHARACTER = 1
    CATEGORY_CORPORATION = 2
    CATEGORY_ALLIANCE = 3
    CATEGORY_FACTION = 4
    CATEGORY_OTHER = 5

    CATEGORY_CHOICES = [
        (CATEGORY_CHARACTER, 'character'),
        (CATEGORY_CORPORATION, 'corporation'),
        (CATEGORY_ALLIANCE, 'alliance'),
        (CATEGORY_FACTION, 'faction'),
        (CATEGORY_OTHER, 'other'),
    ]

    id = models.PositiveIntegerField(
        primary_key=True, help_text='Eve Online ID'
    )
    category = models.IntegerField(choices=CATEGORY_CHOICES)
    name = models.CharField(
        max_length=255,
        null=True,
        default=None,
        blank=True
    )

    objects = EveEntityManager()

    def __str__(self):
        return str(self.name)

    def __repr__(self):
        return '{}(id={}, category=\'{}\', name=\'{}\')'.format(
            self.__class__.__name__,
            self.id,
            self.get_category_display(),
            self.name
        )

    def profile_url(self) -> str:
        """returns link to website with profile info about this entity"""        
        if self.category == self.CATEGORY_CORPORATION:
            url = dotlan.corporation_url(self.name)
        elif self.category == self.CATEGORY_ALLIANCE:
            url = dotlan.alliance_url(self.name)        
        else:
            url = ''
        return url

    @classmethod
    def get_matching_entity_category(cls, type_name) -> int:
        """returns category for given ESI name"""
        match = None
        for x in cls.CATEGORY_CHOICES:
            if type_name == x[1]:
                match = x
                break
        return match[0] if match else cls.CATEGORY_OTHER


class Notification(models.Model):
    """An EVE Online notification about structures"""

    # embed colors
    EMBED_COLOR_INFO = 0x5bc0de
    EMBED_COLOR_SUCCESS = 0x5cb85c
    EMBED_COLOR_WARNING = 0xf0ad4e
    EMBED_COLOR_DANGER = 0xd9534f

    HTTP_CODE_TOO_MANY_REQUESTS = 429

    # event type structure map
    MAP_CAMPAIGN_EVENT_2_TYPE_ID = {
        1: EveType.EVE_TYPE_ID_TCU,
        2: EveType.EVE_TYPE_ID_IHUB
    }

    MAP_TYPE_ID_2_TIMER_STRUCTURE_NAME = {
        2233: 'POCO',
        32226: 'TCU',
        32458: 'I-HUB'
    }

    notification_id = models.BigIntegerField( 
        validators=[MinValueValidator(0)]
    )
    owner = models.ForeignKey(
        'Owner',
        on_delete=models.CASCADE,
        help_text='Corporation that received this notification'
    )
    sender = models.ForeignKey(EveEntity, on_delete=models.CASCADE)
    timestamp = models.DateTimeField()
    notification_type = models.IntegerField(choices=NTYPE_CHOICES)
    text = models.TextField(
        null=True,
        default=None,
        blank=True,
        help_text='Notification details in YAML'
    )
    is_read = models.BooleanField(
        null=True,
        default=None,
        blank=True,
        help_text='True when this notification has read in the eve client'
    )
    is_sent = models.BooleanField(
        default=False,
        blank=True,
        help_text='True when this notification has been forwarded to Discord'
    )
    is_timer_added = models.BooleanField(
        null=True,
        default=None,
        blank=True,
        help_text='True when a timer has been added for this notification'
    )
    last_updated = models.DateTimeField(
        help_text='Date when this notification has last been updated from ESI'
    )
    created = models.DateTimeField(
        null=True,
        default=None,
        blank=True,
        help_text='Date when this notification was first received from ESI'
    )

    class Meta:
        unique_together = (('notification_id', 'owner'),)

    def __str__(self):
        return str(self.notification_id)

    def __repr__(self):
        return '%s(notification_id=%d, owner=\'%s\', notification_type=\'%s\')' % (
            self.__class__.__name__,            
            self.notification_id,
            self.owner,
            self.get_notification_type_display(),
        )

    @property
    def is_alliance_level(self):
        """whether this is an alliance level notification"""
        return self.notification_type in NTYPE_FOR_ALLIANCE_LEVEL
    
    @classmethod
    def get_all_types(cls):
        """returns a set with all supported notification types"""
        return {x[0] for x in NTYPE_CHOICES}

    @classmethod
    def get_all_type_names(cls):
        """returns a set with names of all supported notification types"""
        return {x[1] for x in NTYPE_CHOICES}

    @classmethod
    def get_types_for_timerboard(cls):
        """returns set of types relevant for the timerboard"""
        return _NTYPE_RELEVANT_FOR_TIMERBOARD

    @classmethod
    def get_matching_notification_type(cls, type_name) -> int:
        """returns matching notification type for given name or None"""
        match = None
        for x in NTYPE_CHOICES:
            if type_name == x[1]:
                match = x
                break
        
        return match[0] if match else None

    def get_parsed_text(self) -> dict:
        """returns the notifications's text as dict"""
        return yaml.safe_load(self.text)

    def is_npc_attacking(self):
        """ whether this notification is about a NPC attacking"""
        result = False
        if self.notification_type in [
            NTYPE_ORBITAL_ATTACKED,
            NTYPE_STRUCTURE_UNDER_ATTACK
        ]:
            parsed_text = self.get_parsed_text()
            corporation_id = None
            if self.notification_type == NTYPE_STRUCTURE_UNDER_ATTACK:
                if ('corpLinkData' in parsed_text
                    and len(parsed_text['corpLinkData']) >= 3
                ):
                    corporation_id = int(parsed_text['corpLinkData'][2])

            if self.notification_type == NTYPE_ORBITAL_ATTACKED:
                if 'aggressorCorpID' in parsed_text:
                    corporation_id = int(parsed_text['aggressorCorpID'])

            if 1000000 <= corporation_id <= 2000000:
                result = True

        return result

    def filter_for_npc_attacks(self):
        """true when notification to be filtered out due to npc attacks"""
        return not STRUCTURES_REPORT_NPC_ATTACKS and self.is_npc_attacking()

    def filter_for_alliance_level(self):
        """true when notification to be filtered out due to alliance level"""
        return self.is_alliance_level and not self.owner.is_alliance_main

    def send_to_webhook(self, webhook: Webhook) -> bool:
        """sends this notification to the configured webhook
        returns True if successful, else False
        """        
        add_prefix = make_logger_prefix(
            'notification:{}'.format(self.notification_id)
        )
        logger.info(add_prefix('Trying to sent to webhook: %s' % webhook))
        if self.is_alliance_level:
            avatar_url = self.owner.corporation.alliance.logo_url()
            ticker = self.owner.corporation.alliance.alliance_ticker
        else:
            avatar_url = self.owner.corporation.logo_url()
            ticker = self.owner.corporation.corporation_ticker

        username = gettext('%(ticker)s Notification') % {'ticker': ticker}
        hook = dhooks_lite.Webhook(
            webhook.url, username=username, avatar_url=avatar_url
        )        
        success = False
        try:
            embed = self._generate_embed(webhook.language_code)
        except Exception as ex:
            logger.warning(add_prefix('Failed to generate embed: %s' % ex))
            raise ex
        else:
            if embed.color == self.EMBED_COLOR_DANGER:
                content = '@everyone'
            elif embed.color == self.EMBED_COLOR_WARNING:
                content = '@here'
            else:
                content = None

            success = self._execute_webhook(hook, content, embed, add_prefix)
        return success

    @classmethod
    def _ldap_datetime_2_dt(cls, ldap_dt: int) -> datetime:
        """converts ldap time to datatime"""
        return pytz.utc.localize(datetime.utcfromtimestamp(
            (ldap_dt / 10000000) - 11644473600
        ))

    @classmethod
    def _ldap_timedelta_2_timedelta(cls, ldap_td: int) -> timedelta:
        """converts a ldap timedelta into a dt timedelta"""
        return timedelta(microseconds=ldap_td / 10)

    def _generate_embed(self, language_code: str) -> dhooks_lite.Embed:
        """generates a Discord embed for this notification"""

        logger.info('Creating embed with language = %s' % language_code)
        parsed_text = self.get_parsed_text()
        
        with translation.override(language_code):
            if self.notification_type in [
                NTYPE_STRUCTURE_FUEL_ALERT,
                NTYPE_STRUCTURE_SERVICES_OFFLINE,
                NTYPE_STRUCTURE_WENT_LOW_POWER,
                NTYPE_STRUCTURE_WENT_HIGH_POWER,
                NTYPE_STRUCTURE_UNANCHORING,
                NTYPE_STRUCTURE_UNDER_ATTACK,
                NTYPE_STRUCTURE_LOST_SHIELD,
                NTYPE_STRUCTURE_LOST_ARMOR,
                NTYPE_STRUCTURE_DESTROYED,
                NTYPE_STRUCTURE_ONLINE
            ]:
                title, description, color, thumbnail = \
                    self._gen_embed_structures_1(parsed_text)

            elif self.notification_type in [
                NTYPE_OWNERSHIP_TRANSFERRED,
                NTYPE_STRUCTURE_ANCHORING
            ]:
                title, description, color, thumbnail = \
                    self._gen_embed_structures_2(parsed_text)

            elif self.notification_type in [
                NTYPE_MOONS_AUTOMATIC_FRACTURE,
                NTYPE_MOONS_EXTRACTION_CANCELED,
                NTYPE_MOONS_EXTRACTION_FINISHED,
                NTYPE_MOONS_EXTRACTION_STARTED,
                NTYPE_MOONS_LASER_FIRED
            ]:
                title, description, color, thumbnail = \
                    self._gen_embed_moons(parsed_text)

            elif self.notification_type in [
                NTYPE_ORBITAL_ATTACKED,
                NTYPE_ORBITAL_REINFORCED,
            ]:
                title, description, color, thumbnail = \
                    self._gen_embed_pocos(parsed_text)

            elif self.notification_type in [
                NTYPE_TOWER_ALERT_MSG,
                NTYPE_TOWER_RESOURCE_ALERT_MSG,
            ]:
                title, description, color, thumbnail = \
                    self._gen_embed_poses(parsed_text)

            elif self.notification_type in [
                NTYPE_SOV_ENTOSIS_CAPTURE_STARTED,
                NTYPE_SOV_COMMAND_NODE_EVENT_STARTED,
                NTYPE_SOV_ALL_CLAIM_ACQUIRED_MSG,
                NTYPE_SOV_STRUCTURE_REINFORCED,
                NTYPE_SOV_STRUCTURE_DESTROYED
            ]:
                title, description, color, thumbnail = \
                    self._gen_embed_sov(parsed_text)

            else:
                raise NotImplementedError('type: {}'.format(
                    self.notification_type
                ))

        if STRUCTURES_DEVELOPER_MODE:
            footer = dhooks_lite.Footer(self.notification_id)
        else:
            footer = None

        return dhooks_lite.Embed(
            title=title,
            description=description,
            color=color,
            thumbnail=thumbnail,
            timestamp=self.timestamp,
            footer=footer
        )
    
    def _gen_embed_structures_1(self, parsed_text: dict) -> tuple:
        
        try:
            my_structure = Structure.objects.get(id=parsed_text['structureID'])
            structure_name = my_structure.name
            structure_type = my_structure.eve_type
            structure_solar_system = my_structure.eve_solar_system
        except Structure.DoesNotExist:
            my_structure = None
            structure_name = gettext('(unknown)')
            structure_type, _ = EveType.objects.get_or_create_esi(
                parsed_text['structureTypeID']
            )
            structure_solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
                parsed_text['solarsystemID']
            )

        description = gettext(
            'The %(structure_type)s %(structure_name)s in %(solar_system)s '
        ) % {
            'structure_type': structure_type.name_localized,
            'structure_name': '**%s**' % structure_name,
            'solar_system': self._gen_solar_system_text(structure_solar_system)
        }
        if self.notification_type == NTYPE_STRUCTURE_ONLINE:
            title = gettext('Structure online')
            description += gettext('is now online.')
            color = self.EMBED_COLOR_SUCCESS

        if self.notification_type == NTYPE_STRUCTURE_FUEL_ALERT:
            title = gettext('Structure fuel alert')
            description += gettext('has less then 24hrs fuel left.')
            color = self.EMBED_COLOR_WARNING

        elif self.notification_type == NTYPE_STRUCTURE_SERVICES_OFFLINE:
            title = gettext('Structure services off-line')
            description += gettext('has all services off-lined.')
            if my_structure and my_structure.structureservice_set.count() > 0:
                qs = my_structure.structureservice_set.all().order_by('name')
                services_list = '\n'.join([x.name for x in qs])
                description += '\n*{}*'.format(
                    services_list
                )

            color = self.EMBED_COLOR_DANGER

        elif self.notification_type == NTYPE_STRUCTURE_WENT_LOW_POWER:
            title = gettext('Structure low power')
            description += gettext('went to low power mode.')
            color = self.EMBED_COLOR_WARNING

        elif self.notification_type == NTYPE_STRUCTURE_WENT_HIGH_POWER:
            title = gettext('Structure full power')
            description += gettext('went to full power mode.')
            color = self.EMBED_COLOR_SUCCESS

        elif self.notification_type == NTYPE_STRUCTURE_UNANCHORING:
            title = gettext('Structure un-anchoring')
            unanchored_at = self.timestamp \
                + self._ldap_timedelta_2_timedelta(parsed_text['timeLeft'])
            description += gettext(
                'has started un-anchoring. '
                'It will be fully un-anchored at: %s'
            ) % unanchored_at.strftime(DATETIME_FORMAT)
            color = self.EMBED_COLOR_INFO

        elif self.notification_type == NTYPE_STRUCTURE_UNDER_ATTACK:
            title = gettext('Structure under attack')
            description += gettext('is under attack by %s') \
                % self._get_attacker_link(parsed_text)
            color = self.EMBED_COLOR_DANGER

        elif self.notification_type == NTYPE_STRUCTURE_LOST_SHIELD:
            title = gettext('Structure lost shield')
            timer_ends_at = self.timestamp \
                + self._ldap_timedelta_2_timedelta(parsed_text['timeLeft'])
            description += gettext(
                'has lost its shields. Armor timer end at: %s'
            ) % timer_ends_at.strftime(DATETIME_FORMAT)
            color = self.EMBED_COLOR_DANGER

        elif self.notification_type == NTYPE_STRUCTURE_LOST_ARMOR:
            title = gettext('Structure lost armor')
            timer_ends_at = self.timestamp \
                + self._ldap_timedelta_2_timedelta(parsed_text['timeLeft'])
            description += gettext(
                'has lost its armor. Hull timer end at: %s'
            ) % timer_ends_at.strftime(DATETIME_FORMAT)
            color = self.EMBED_COLOR_DANGER

        elif self.notification_type == NTYPE_STRUCTURE_DESTROYED:
            title = gettext('Structure destroyed')
            description += gettext('has been destroyed.')
            color = self.EMBED_COLOR_DANGER
        
        thumbnail = dhooks_lite.Thumbnail(structure_type.icon_url())
        return title, description, color, thumbnail
    
    def _gen_embed_structures_2(self, parsed_text: dict) -> tuple:
        structure_type, _ = EveType.objects.get_or_create_esi(
            parsed_text['structureTypeID']
        )        
        if self.notification_type == NTYPE_OWNERSHIP_TRANSFERRED:            
            solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
                parsed_text['solarSystemID']
            )
            description = gettext(
                'The %(structure_type)s %(structure_name)s '
                'in %(solar_system)s '
            ) % {
                'structure_type': structure_type.name,
                'structure_name': '**%s**' % parsed_text['structureName'],
                'solar_system': self._gen_solar_system_text(solar_system)
            }
            from_corporation, _ = \
                EveEntity.objects.get_or_create_esi(
                    parsed_text['oldOwnerCorpID']
                )
            to_corporation, _ = \
                EveEntity.objects.get_or_create_esi(
                    parsed_text['newOwnerCorpID']
                )
            character, _ = \
                EveEntity.objects.get_or_create_esi(
                    parsed_text['charID']
                )
            description += gettext(
                'has been transferred from %(from_corporation)s '
                'to %(to_corporation)s by %(character)s.'
            ) % {
                'from_corporation': self._gen_corporation_link(
                    from_corporation.name
                ),
                'to_corporation': self._gen_corporation_link(
                    to_corporation.name
                ),
                'character': character.name
            }
            title = gettext('Ownership transferred')
            color = self.EMBED_COLOR_INFO

        elif self.notification_type == NTYPE_STRUCTURE_ANCHORING:            
            solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
                parsed_text['solarsystemID']
            )
            description = gettext(
                '%(structure_type)s has started anchoring '
                'in %(solar_system)s. '
            ) % {
                'structure_type': structure_type.name_localized,
                'solar_system': self._gen_solar_system_text(solar_system)
            }
            if not solar_system.is_null_sec:
                unanchored_at = self.timestamp + timedelta(hours=24)
                description += 'The anchoring timer ends at: {}'.format(
                    unanchored_at.strftime(DATETIME_FORMAT)
                )
            title = gettext('Structure anchoring')
            color = self.EMBED_COLOR_INFO

        thumbnail = dhooks_lite.Thumbnail(structure_type.icon_url())
        return title, description, color, thumbnail
    
    def _gen_embed_moons(self, parsed_text: dict) -> tuple:
        moon, _ = EveMoon.objects.get_or_create_esi(parsed_text['moonID'])        
        solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
            parsed_text['solarSystemID']
        )
        solar_system_link = self._gen_solar_system_text(solar_system)
        structure_name = parsed_text['structureName']
        if self.notification_type == NTYPE_MOONS_EXTRACTION_STARTED:
            started_by, _ = EveEntity.objects.get_or_create_esi(
                parsed_text['startedBy']
            )
            ready_time = self._ldap_datetime_2_dt(parsed_text['readyTime'])
            auto_time = self._ldap_datetime_2_dt(parsed_text['autoTime'])
            title = gettext('Moon mining extraction started')
            description = gettext(
                'A moon mining extraction has been started '
                'for %(structure_name)s at %(moon)s in %(solar_system)s. '
                'Extraction was started by %(character)s.\n'
                'The chunk will be ready on location at %(ready_time)s, '
                'and will autofracture on %(auto_time)s.\n'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': moon.name_localized,
                'solar_system': solar_system_link,
                'character': started_by,
                'ready_time': ready_time.strftime(DATETIME_FORMAT),
                'auto_time': auto_time.strftime(DATETIME_FORMAT)
            }                
            color = self.EMBED_COLOR_INFO

        elif (self.notification_type == NTYPE_MOONS_EXTRACTION_FINISHED):
            auto_time = self._ldap_datetime_2_dt(parsed_text['autoTime'])
            title = gettext('Extraction finished')
            description = gettext(
                'The extraction for %(structure_name)s at %(moon)s '
                'in %(solar_system)s is finished and the chunk is ready '
                'to be shot at.\n'
                'The chunk will automatically fracture on %(auto_time)s.'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': moon.name_localized,
                'solar_system': solar_system_link,
                'auto_time': auto_time.strftime(DATETIME_FORMAT)
            }                
            color = self.EMBED_COLOR_INFO

        elif self.notification_type == NTYPE_MOONS_AUTOMATIC_FRACTURE:
            title = gettext('Automatic Fracture')
            description = gettext(
                'The moondrill fitted to %(structure_name)s at %(moon)s'
                ' in %(solar_system)s has automatically been fired '
                'and the moon products are ready to be harvested.\n'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': moon.name_localized,
                'solar_system': solar_system_link
            }                
            color = self.EMBED_COLOR_SUCCESS

        elif (self.notification_type == NTYPE_MOONS_EXTRACTION_CANCELED):
            if parsed_text['cancelledBy']:
                cancelled_by, _ = EveEntity.objects.get_or_create_esi(
                    parsed_text['cancelledBy']
                )
            else:
                cancelled_by = gettext('(unknown)')
            title = gettext('Extraction cancelled')
            description = gettext(
                'An ongoing extraction for %(structure_name)s at %(moon)s '
                'in %(solar_system)s has been cancelled by %(character)s.'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': moon.name_localized,
                'solar_system': solar_system_link,
                'character': cancelled_by
            }
        
            color = self.EMBED_COLOR_WARNING

        elif self.notification_type == NTYPE_MOONS_LASER_FIRED:
            fired_by, _ = EveEntity.objects.get_or_create_esi(
                parsed_text['firedBy']
            )
            title = gettext('Moondrill fired')
            description = gettext(
                'The moondrill fitted to %(structure_name)s at %(moon)s '
                'in %(solar_system)s has been fired by %(character)s '
                'and the moon products are ready to be harvested.'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': moon.name_localized,
                'solar_system': solar_system_link,
                'character': fired_by
            }
            color = self.EMBED_COLOR_SUCCESS

        structure_type, _ = EveType.objects.get_or_create_esi(
            parsed_text['structureTypeID']
        )
        thumbnail = dhooks_lite.Thumbnail(structure_type.icon_url())
        return title, description, color, thumbnail

    def _gen_embed_pocos(self, parsed_text: dict) -> tuple:        
        planet, _ = EvePlanet.objects.get_or_create_esi(
            parsed_text['planetID']
        )
        structure_type, _ = EveType.objects.get_or_create_esi(
            EveType.EVE_TYPE_ID_POCO
        )        
        solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
            parsed_text['solarSystemID']
        )
        solar_system_link = self._gen_solar_system_text(solar_system)
        aggressor_link = self._get_aggressor_link(parsed_text)

        if self.notification_type == NTYPE_ORBITAL_ATTACKED:
            title = gettext('Orbital under attack')
            description = gettext(
                'The %(structure_type)s at %(planet)s in %(solar_system)s '
                'is under attack by %(aggressor)s.'
            ) % {
                'structure_type': structure_type.name_localized,
                'planet': planet.name_localized,
                'solar_system': solar_system_link,
                'aggressor': aggressor_link
            }
            color = self.EMBED_COLOR_WARNING

        elif self.notification_type == NTYPE_ORBITAL_REINFORCED:
            reinforce_exit_time = \
                self._ldap_datetime_2_dt(parsed_text['reinforceExitTime'])
            title = gettext('Orbital reinforced')
            description = gettext(
                'The %(structure_type)s at %(planet)s in %(solar_system)s '
                'has been reinforced by %(aggressor)s '
                'and will come out at: %(date)s.'
            ) % {
                'structure_type': structure_type.name_localized,
                'planet': planet.name_localized,
                'solar_system': solar_system_link,
                'aggressor': aggressor_link,
                'date': reinforce_exit_time.strftime(DATETIME_FORMAT)
            }
            color = self.EMBED_COLOR_DANGER
        
        thumbnail = dhooks_lite.Thumbnail(structure_type.icon_url())
        return title, description, color, thumbnail
    
    def _gen_embed_poses(self, parsed_text: dict) -> tuple:        
        eve_moon, _ = EveMoon.objects.get_or_create_esi(
            parsed_text['moonID']
        )
        structure_type, _ = EveType.objects.get_or_create_esi(
            parsed_text['typeID']
        )        
        solar_system_link = self._gen_solar_system_text(
            eve_moon.eve_solar_system
        )
        qs_structures = Structure.objects.filter(eve_moon=eve_moon)
        if qs_structures.exists():
            structure_name = qs_structures.first().name
        else:
            structure_name = structure_type.name_localized

        if self.notification_type == NTYPE_TOWER_ALERT_MSG:
            aggressor_link = self._get_aggressor_link(parsed_text)
            damage_labels = [
                ('shield', gettext('shield')), 
                ('armor', gettext('armor')), 
                ('hull', gettext('hull')),                    
            ]
            damage_parts = list()
            for prop in damage_labels:
                prop_yaml = prop[0] + 'Value'
                if prop_yaml in parsed_text:
                    damage_parts.append(
                        '{}: {:.0f}%'.format(
                            prop[1],
                            parsed_text[prop_yaml] * 100
                        )
                    )
            damage_text = ' | '.join(damage_parts)
            title = gettext('Starbase under attack')
            description = gettext(
                'The starbase %(structure_name)s at %(moon)s '
                'in %(solar_system)s is under attack by %(aggressor)s.\n'
                '%(damage_text)s'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': eve_moon.name_localized,
                'solar_system': solar_system_link,
                'aggressor': aggressor_link,
                'damage_text': damage_text,
            }
            color = self.EMBED_COLOR_WARNING

        elif self.notification_type == NTYPE_TOWER_RESOURCE_ALERT_MSG:
            quantity = parsed_text['wants'][0]['quantity']
            title = gettext('Starbase low on fuel')
            description = gettext(
                'The starbase %(structure_name)s at %(moon)s '
                'in %(solar_system)s is low on fuel. '
                'It has %(quantity)d fuel blocks left.'
            ) % {
                'structure_name': '**%s**' % structure_name,
                'moon': eve_moon.name_localized,
                'solar_system': solar_system_link,
                'quantity': quantity
            }
            color = self.EMBED_COLOR_WARNING
        
        thumbnail = dhooks_lite.Thumbnail(structure_type.icon_url())
        return title, description, color, thumbnail
    
    def _gen_embed_sov(self, parsed_text: dict) -> tuple:        
        solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
            parsed_text['solarSystemID']
        )
        solar_system_link = self._gen_solar_system_text(solar_system)

        if 'structureTypeID' in parsed_text:
            structure_type_id = parsed_text['structureTypeID']
        elif 'campaignEventType' in parsed_text:
            structure_type_id = self._get_type_id_from_event_type(
                parsed_text['campaignEventType']
            )
        else:
            structure_type_id = EveType.EVE_TYPE_ID_TCU

        structure_type, _ = EveType.objects.get_or_create_esi(
            structure_type_id
        )
        structure_type_name = structure_type.name_localized        
        sov_owner_link = self._gen_alliance_link(
            self.owner.corporation.alliance.alliance_name
        )
        if self.notification_type == NTYPE_SOV_ENTOSIS_CAPTURE_STARTED:
            title = gettext(
                '%(structure_type)s in %(solar_system)s is being captured'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system.name_localized
            }
            description = gettext(
                'A capsuleer has started to influence the %(type)s '
                'in %(solar_system)s belonging to %(owner)s '
                'with an Entosis Link.'
            ) % {
                'type': structure_type_name,
                'solar_system': solar_system_link,
                'owner': sov_owner_link
            }
            color = self.EMBED_COLOR_WARNING

        elif (self.notification_type == NTYPE_SOV_COMMAND_NODE_EVENT_STARTED):
            title = gettext(
                'Command nodes for %(structure_type)s in %(solar_system)s '
                'have begun to decloak'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system.name_localized
            }
            description = gettext(
                'Command nodes for %(structure_type)s in %(solar_system)s '
                'can now be found throughout '
                'the %(constellation)s constellation'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system_link,
                'constellation': solar_system.eve_constellation.name_localized
            }
            color = self.EMBED_COLOR_WARNING

        elif self.notification_type == NTYPE_SOV_ALL_CLAIM_ACQUIRED_MSG:
            alliance, _ = EveEntity.objects.get_or_create_esi(
                parsed_text['allianceID']
            )
            corporation, _ = EveEntity.objects.get_or_create_esi(
                parsed_text['corpID']
            )
            title = gettext(
                'DED Sovereignty claim acknowledgment: %s'
            ) % solar_system.name_localized
            
            description = gettext(
                'DED now officially acknowledges that your '
                'member corporation %(corporation)s has claimed '
                'sovereignty on behalf of %(alliance)s in %(solar_system)s.'
            ) % {
                'corporation': self._gen_corporation_link(corporation.name),
                'alliance': self._gen_alliance_link(alliance.name),
                'solar_system': solar_system_link
            }
            color = self.EMBED_COLOR_SUCCESS

        elif self.notification_type == NTYPE_SOV_STRUCTURE_REINFORCED:
            timer_starts = self._ldap_datetime_2_dt(
                parsed_text['decloakTime']
            )
            title = gettext(
                '%(structure_type)s in %(solar_system)s '
                'has entered reinforced mode'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system.name_localized
            }
            description = gettext(
                'The %(structure_type)s in %(solar_system)s belonging '
                'to %(owner)s has been reinforced by '
                'hostile forces and command nodes '
                'will begin decloaking at %(date)s'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system_link,
                'owner': sov_owner_link,
                'date': timer_starts.strftime(DATETIME_FORMAT)
            }
            color = self.EMBED_COLOR_DANGER

        elif self.notification_type == NTYPE_SOV_STRUCTURE_DESTROYED:
            title = gettext(
                '%(structure_type)s in %(solar_system)s has been destroyed'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system.name_localized
            }
            description = gettext(
                'The command nodes for %(structure_type)s '
                'in %(solar_system)s belonging to %(owner)s have been '
                'destroyed by hostile forces.'
            ) % {
                'structure_type': '**%s**' % structure_type_name,
                'solar_system': solar_system_link,
                'owner': sov_owner_link
            }
            color = self.EMBED_COLOR_DANGER
        
        thumbnail = dhooks_lite.Thumbnail(structure_type.icon_url())
        return title, description, color, thumbnail

    @classmethod
    def _gen_solar_system_text(cls, solar_system: EveSolarSystem) -> str:
        text = '[{}]({}) ({})'.format(
            solar_system.name_localized,
            dotlan.solar_system_url(solar_system.name),
            solar_system.eve_constellation.eve_region.name_localized
        )
        return text

    @classmethod
    def _gen_alliance_link(cls, alliance_name):
        return '[{}]({})'.format(
            alliance_name, dotlan.alliance_url(alliance_name)
        )

    @classmethod
    def _gen_corporation_link(cls, corporation_name):
        return '[{}]({})'.format(
            corporation_name, dotlan.corporation_url(corporation_name)
        )

    @classmethod
    def _get_attacker_link(cls, parsed_text):
        """returns the attacker link from a parsed_text
        For Upwell structures only
        """
        if "allianceName" in parsed_text:
            name = cls._gen_alliance_link(parsed_text['allianceName'])
        elif "corpName" in parsed_text:
            name = cls._gen_corporation_link(parsed_text['corpName'])
        else:
            name = "(unknown)"

        return name

    @classmethod
    def _get_aggressor_link(cls, parsed_text: dict) -> str:
        """returns the aggressor link from a parsed_text
        for POS and POCOs only
        """
        if 'aggressorAllianceID' in parsed_text:
            key = 'aggressorAllianceID'
        elif 'aggressorCorpID' in parsed_text:
            key = 'aggressorCorpID'
        elif 'aggressorID' in parsed_text:
            key = 'aggressorID'
        else:
            return '(Unknown aggressor)'

        entity, _ = EveEntity.objects.get_or_create_esi(parsed_text[key])
        return '[{}]({})'.format(entity.name, entity.profile_url())

    @classmethod
    def _get_type_id_from_event_type(cls, event_type: int) -> int:
        if event_type in cls.MAP_CAMPAIGN_EVENT_2_TYPE_ID:
            return cls.MAP_CAMPAIGN_EVENT_2_TYPE_ID[event_type]
        else:
            return None
    
    def _execute_webhook(
        self, hook, content, embed, add_prefix
    ) -> bool:
        """executes webhook for sending the message, will retry on errors
        
        Sets this notification as "sent" if successful
        
        returns True/False on success
        """
        success = False
        max_retries = STRUCTURES_NOTIFICATION_MAX_RETRIES
        for retry_count in range(max_retries + 1):
            if retry_count > 0:
                logger.warn(add_prefix('Retry {} / {}'.format(
                    retry_count, max_retries
                )))
            try:
                res = hook.execute(
                    content=content, embeds=[embed], wait_for_response=True
                )
                if res.status_ok:
                    self.is_sent = True
                    self.save()
                    success = True
                    break

                elif res.status_code == self.HTTP_CODE_TOO_MANY_REQUESTS:
                    if 'retry_after' in res.content:
                        retry_after = res.content['retry_after'] / 1000
                    else:
                        retry_after = STRUCTURES_NOTIFICATION_WAIT_SEC
                    logger.warn(add_prefix(
                        'rate limited - retry after %d secs' % retry_after
                    ))
                    sleep(retry_after)

                else:
                    logger.warn(add_prefix(
                        'HTTP error {} while trying '
                        'to send notifications'.format(res.status_code) 
                    ))
                    if retry_count < max_retries + 1:
                        sleep(STRUCTURES_NOTIFICATION_WAIT_SEC)

            except Exception as ex:
                logger.warn(add_prefix(
                    'Unexpected issue when trying to send message: %s' % ex
                ))
                if settings.DEBUG:
                    raise ex
                else:
                    break

        return success

    def process_for_timerboard(self, token: Token = None) -> bool:
        """add/removes a timer related to this notification for some types
        returns True when a timer was processed, else False
        """
        success = False
        if self.notification_type in _NTYPE_RELEVANT_FOR_TIMERBOARD:
            parsed_text = self.get_parsed_text()
            try:
                with translation.override(STRUCTURES_DEFAULT_LANGUAGE):
                    if self.notification_type in [
                        NTYPE_STRUCTURE_LOST_ARMOR,
                        NTYPE_STRUCTURE_LOST_SHIELD,
                    ]:
                        timer = self._gen_timer_structure_reinforcement(
                            parsed_text, token
                        )
                    elif self.notification_type == NTYPE_STRUCTURE_ANCHORING:
                        timer = self._gen_timer_structure_anchoring(parsed_text)
                    elif self.notification_type == NTYPE_SOV_STRUCTURE_REINFORCED:
                        timer = self._gen_timer_sov_reinforcements(parsed_text)
                    elif self.notification_type == NTYPE_ORBITAL_REINFORCED:
                        timer = self._gen_timer_orbital_reinforcements(parsed_text)
                    elif self.notification_type in [
                        NTYPE_MOONS_EXTRACTION_STARTED,
                        NTYPE_MOONS_EXTRACTION_CANCELED
                    ]:
                        if not STRUCTURES_MOON_EXTRACTION_TIMERS_ENABLED:
                            timer = None
                        else:
                            timer = self._gen_timer_moon_extraction(parsed_text)
                    else:
                        raise NotImplementedError()

                if timer:
                    timer.save()
                    logger.info(
                        '{}: added timer related notification'.format(
                            self.notification_id
                        )
                    )
                    self.is_timer_added = True
                    self.save()
                    success = True

            except Exception as ex:
                logger.exception(
                    '{}: Failed to add timer from notification: {}'.format(
                        self.notification_id,
                        ex
                    )
                )
                if settings.DEBUG:
                    raise ex

        return success
    
    def _gen_timer_structure_reinforcement(self, parsed_text, token):
        """generate timer for structure reinforcements"""
        structure_obj, _ = Structure.objects.get_or_create_esi(
            parsed_text['structureID'], token
        )
        eve_time = self.timestamp \
            + self._ldap_timedelta_2_timedelta(
                parsed_text['timeLeft']
            )
        if self.notification_type == NTYPE_STRUCTURE_LOST_SHIELD:
            details = gettext('Armor timer')
        elif self.notification_type == NTYPE_STRUCTURE_LOST_ARMOR:
            details = gettext('Final timer')

        return Timer(
            details=details,
            system=structure_obj.eve_solar_system.name,
            planet_moon='',
            structure=structure_obj.eve_type.name,
            objective='Friendly',
            eve_time=eve_time,
            eve_corp=self.owner.corporation,
            corp_timer=STRUCTURES_TIMERS_ARE_CORP_RESTRICTED
        )
    
    def _gen_timer_structure_anchoring(self, parsed_text):
        """generate timer for structure anchoring"""
        structure_type, _ = EveType.objects.get_or_create_esi(
            parsed_text['structureTypeID']
        )
        solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
            parsed_text['solarsystemID']
        )
        if not solar_system.is_null_sec:
            eve_time = self.timestamp + timedelta(hours=24)
            timer = Timer(
                details=gettext('Anchor timer'),
                system=solar_system.name,
                planet_moon='',
                structure=structure_type.name,
                objective='Friendly',
                eve_time=eve_time,
                eve_corp=self.owner.corporation,
                corp_timer=STRUCTURES_TIMERS_ARE_CORP_RESTRICTED
            )
        else:
            timer = None
        
        return timer
    
    def _gen_timer_sov_reinforcements(self, parsed_text):
        """generate timer for sov reinforcements"""
        solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
            parsed_text['solarSystemID']
        )
        event_type = parsed_text['campaignEventType']
        if event_type in self.MAP_CAMPAIGN_EVENT_2_TYPE_ID:
            structure_type_name = \
                self.MAP_TYPE_ID_2_TIMER_STRUCTURE_NAME[
                    self.MAP_CAMPAIGN_EVENT_2_TYPE_ID[event_type]
                ]
        else:
            structure_type_name = 'Other'

        eve_time = self._ldap_datetime_2_dt(parsed_text['decloakTime'])
        return Timer(
            details=gettext('Sov timer'),
            system=solar_system.name,
            planet_moon='',
            structure=structure_type_name,
            objective='Friendly',
            eve_time=eve_time,
            eve_corp=self.owner.corporation,
            corp_timer=STRUCTURES_TIMERS_ARE_CORP_RESTRICTED
        )
    
    def _gen_timer_orbital_reinforcements(self, parsed_text):
        """generate timer for orbital reinforcements"""
        solar_system, _ = EveSolarSystem.objects.get_or_create_esi(
            parsed_text['solarSystemID']
        )
        planet, _ = EvePlanet.objects.get_or_create_esi(
            parsed_text['planetID']
        )
        eve_time = self._ldap_datetime_2_dt(
            parsed_text['reinforceExitTime']
        )
        return Timer(
            details=gettext('Final timer'),
            system=solar_system.name,
            planet_moon=planet.name,
            structure='POCO',
            objective='Friendly',
            eve_time=eve_time,
            eve_corp=self.owner.corporation,
            corp_timer=STRUCTURES_TIMERS_ARE_CORP_RESTRICTED
        )
    
    def _gen_timer_moon_extraction(self, parsed_text):
        """generate timer for moon mining extractions"""
        solar_system, _ = \
            EveSolarSystem.objects.get_or_create_esi(
                parsed_text['solarSystemID']
            )
        moon, _ = EveMoon.objects.get_or_create_esi(
            parsed_text['moonID']
        )
        if 'readyTime' in parsed_text:
            eve_time = self._ldap_datetime_2_dt(
                parsed_text['readyTime']
            )
        else:
            eve_time = None
        details = gettext('Extraction ready')
        system = solar_system.name
        planet_moon = moon.name
        structure_type_name = 'Moon Mining Cycle'
        objective = 'Friendly'

        if self.notification_type == NTYPE_MOONS_EXTRACTION_STARTED:
            timer = Timer(
                details=details,
                system=system,
                planet_moon=planet_moon,
                structure=structure_type_name,
                objective=objective,
                eve_time=eve_time,
                eve_corp=self.owner.corporation,
                corp_timer=STRUCTURES_TIMERS_ARE_CORP_RESTRICTED
            )
        elif self.notification_type == NTYPE_MOONS_EXTRACTION_CANCELED:
            timer = None
            notifications_qs = Notification.objects\
                .filter(
                    notification_type=NTYPE_MOONS_EXTRACTION_STARTED,
                    owner=self.owner,
                    is_timer_added=True,
                    timestamp__lte=self.timestamp
                )\
                .order_by('-timestamp')

            for notification in notifications_qs:
                parsed_text_2 = notification.get_parsed_text()
                my_structure_type_id = parsed_text_2['structureTypeID']
                if my_structure_type_id == parsed_text['structureTypeID']:
                    eve_time = self._ldap_datetime_2_dt(
                        parsed_text_2['readyTime']
                    )
                    timer_query = Timer.objects\
                        .filter(
                            details=details,
                            system=system,
                            planet_moon=planet_moon,
                            structure=structure_type_name,
                            objective=objective,
                            eve_time=eve_time
                        )
                    deleted_count, _ = timer_query.delete()
                    logger.info(
                        '{}: removed {} timer related to notification'.format(
                            deleted_count,
                            self.notification_id
                        )
                    )
                    self.is_timer_added = False
                    self.save()

        return timer
