# pylint: disable = redefined-builtin

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional, Tuple

from bravado.exception import HTTPError

from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db import models, transaction
from django.db.models import Case, Q, Value, When
from django.utils.translation import gettext_lazy as _
from esi.models import Token
from eveuniverse.models import EveEntity
from eveuniverse.tasks import create_eve_entities

from allianceauth.eveonline.models import EveCharacter
from allianceauth.notifications import notify
from allianceauth.services.hooks import get_extension_logger
from app_utils.helpers import chunks
from app_utils.logging import LoggerAddTag

from . import __title__
from .app_settings import SR_NOTIFICATIONS_ENABLED
from .constants import CreateCharacterRequestResult, OperationMode
from .core import app_config
from .core.contact_types import ContactTypeId
from .providers import esi

if TYPE_CHECKING:
    from .models import AbstractStandingsRequest, ContactSet, StandingRequest

logger = LoggerAddTag(get_extension_logger(__name__), __title__)


class EsiContactsContainer:
    """Converts raw contacts and contact labels data from ESI into an object"""

    class EsiLabel:
        def __init__(self, json):
            self.id = json["label_id"]
            self.name = json["label_name"]

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

        def __repr__(self) -> str:
            return str(self)

    class EsiContact:
        def __init__(self, json, labels, names_info):
            self.id = json["contact_id"]
            self.name = names_info[self.id] if self.id in names_info else ""
            self.standing = json["standing"]
            self.in_watchlist = json["in_watchlist"] if "in_watchlist" in json else None
            self.label_ids = (
                json["label_ids"]
                if "label_ids" in json and json["label_ids"] is not None
                else []
            )
            # list of labels
            self.labels = [label for label in labels if label.id in self.label_ids]

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

        def __repr__(self):
            return str(self)

    def __init__(self, token, owner_character):
        self.contacts = []
        self.labels = []

        if app_config.operation_mode() is OperationMode.ALLIANCE:
            if not owner_character.alliance_id:
                raise RuntimeError(
                    "{owner_character}: owner character is not a member of an alliance"
                )
            labels = esi.client.Contacts.get_alliances_alliance_id_contacts_labels(
                alliance_id=owner_character.alliance_id,
                token=token.valid_access_token(),
            ).results()
            self.labels = [self.EsiLabel(label) for label in labels]
            contacts = esi.client.Contacts.get_alliances_alliance_id_contacts(
                alliance_id=owner_character.alliance_id,
                token=token.valid_access_token(),
            ).results()

        elif app_config.operation_mode() is OperationMode.CORPORATION:
            labels = (
                esi.client.Contacts.get_corporations_corporation_id_contacts_labels(
                    corporation_id=owner_character.corporation_id,
                    token=token.valid_access_token(),
                ).results()
            )
            self.labels = [self.EsiLabel(label) for label in labels]
            contacts = esi.client.Contacts.get_corporations_corporation_id_contacts(
                corporation_id=owner_character.corporation_id,
                token=token.valid_access_token(),
            ).results()
        else:
            raise NotImplementedError()

        logger.debug("Got %d contacts in total", len(contacts))
        entity_ids = [contact["contact_id"] for contact in contacts]
        resolver = EveEntity.objects.bulk_resolve_names(entity_ids)
        self.contacts = [
            self.EsiContact(contact, self.labels, resolver._names_map)
            for contact in contacts
        ]


class ContactSetManager(models.Manager):
    def create_new_from_api(self) -> Optional[ContactSet]:
        """fetches contacts with standings for configured alliance
        or corporation from ESI and stores them as newly created ContactSet

        Returns new ContactSet on success, else None
        """
        owner_character = app_config.owner_character()
        token: Token = (
            Token.objects.filter(character_id=owner_character.character_id)
            .require_scopes(self.model.required_esi_scope())
            .require_valid()
            .first()
        )
        if not token:
            logger.warning("Token for standing char could not be found")
            return None

        try:
            contacts_wrap = EsiContactsContainer(token, owner_character)
        except HTTPError as ex:
            logger.exception(
                "APIError occurred while trying to query api server: %s", ex
            )
            return None

        with transaction.atomic():
            contacts_set = self.create()
            self._add_labels_from_api(contacts_set, contacts_wrap.labels)
            self._add_contacts_from_api(contacts_set, contacts_wrap.contacts)

        return contacts_set

    def _add_labels_from_api(self, contact_set: ContactSet, labels):
        """Add the list of labels to the given ContactSet

        contact_set: ContactSet instance
        labels: Label dictionary
        """
        from .models import ContactLabel

        contact_labels = [
            ContactLabel(label_id=label.id, name=label.name, contact_set=contact_set)
            for label in labels
        ]
        ContactLabel.objects.bulk_create(contact_labels, ignore_conflicts=True)

    def _add_contacts_from_api(self, contact_set, contacts):
        """Add all contacts to the given ContactSet
        Labels _MUST_ be added before adding contacts

        :param contact_set: Django ContactSet to add contacts to
        :param contacts: List of _ContactsWrapper.Contact to add
        """
        from .models import Contact

        for contact in contacts:
            eve_entity, _ = EveEntity.objects.get_or_create_esi(id=contact.id)
            obj = Contact.objects.create(
                contact_set=contact_set,
                eve_entity=eve_entity,
                standing=contact.standing,
            )
            flat_labels = [label.id for label in contact.labels]
            labels = contact_set.labels.filter(label_id__in=flat_labels)
            obj.labels.add(*labels)


class ContactQuerySet(models.QuerySet):
    def filter_characters(self):
        return self.filter(eve_entity__category=EveEntity.CATEGORY_CHARACTER)

    def filter_corporations(self):
        return self.filter(eve_entity__category=EveEntity.CATEGORY_CORPORATION)

    def filter_alliances(self):
        return self.filter(eve_entity__category=EveEntity.CATEGORY_ALLIANCE)


class AbstractStandingsRequestQuerySet(models.QuerySet):
    def annotate_is_pending(self) -> models.QuerySet:
        return self.annotate(
            is_pending_annotated=Case(
                When(Q(action_date__isnull=True) & Q(is_effective=False), then=True),
                default=Value(False),
                output_field=models.BooleanField(),
            )
        )

    def annotate_is_actioned(self) -> models.QuerySet:
        return self.annotate(
            is_actioned_annotated=Case(
                When(Q(action_date__isnull=False) & Q(is_effective=False), then=True),
                default=Value(False),
                output_field=models.BooleanField(),
            )
        )


class _AbstractStandingsRequestManagerBase(models.Manager):
    def filter_characters(self) -> models.QuerySet:
        return self.filter(contact_type_id__in=ContactTypeId.character_ids())

    def filter_corporations(self) -> models.QuerySet:
        return self.filter(contact_type_id=ContactTypeId.CORPORATION)

    def process_requests(self) -> None:
        """Process all the Standing requests/revocation objects"""
        from .models import AbstractStandingsRequest

        if self.model is AbstractStandingsRequest:
            raise TypeError("Can not be called from abstract objects")

        organization = app_config.standings_source_entity()
        organization_name = organization.name if organization else ""
        query: models.QuerySet[AbstractStandingsRequest] = self.all()
        for standing_request in query:
            contact = EveEntity.objects.get_or_create_esi(
                id=standing_request.contact_id
            )[0]
            is_currently_effective = standing_request.is_effective
            is_satisfied_standing = standing_request.evaluate_effective_standing()
            if is_satisfied_standing and not is_currently_effective:
                if SR_NOTIFICATIONS_ENABLED:
                    self._notify_user_about_standing_change(
                        organization_name=organization_name,
                        standing_request=standing_request,
                        contact=contact,
                    )

                # if this was a revocation the standing requests need to be remove
                # to indicate that this character no longer has standing
                if standing_request.is_standing_revocation:
                    self._remove_standing_request_after_revocation(standing_request)

            elif is_satisfied_standing:
                # Just catching all other contact types (corps/alliances)
                # that are set effective
                pass

            elif not is_satisfied_standing and is_currently_effective:
                # Effective standing no longer effective
                self._removing_effective_standing(standing_request)

            else:
                # Check the standing hasn't been set actioned
                # and not updated in game
                actioned_timeout = standing_request.check_actioned_timeout()
                if actioned_timeout is not None and actioned_timeout:
                    logger.info(
                        "Standing request for contact ID %d has timed out "
                        "and will be reset",
                        standing_request.contact_id,
                    )
                    if SR_NOTIFICATIONS_ENABLED:
                        self._notify_user_about_timed_out_request(
                            standing_request, contact, actioned_timeout
                        )

    def _notify_user_about_standing_change(
        self,
        organization_name: str,
        standing_request: AbstractStandingsRequest,
        contact: EveEntity,
    ):
        if standing_request.is_standing_request:
            notify(
                user=standing_request.user,
                title=_("%s: Standing with %s now in effect")
                % (__title__, contact.name),
                message=_(
                    "'%(organization_name)s' now has blue standing with "
                    "your alt %(contact_category)s '%(contact_name)s'. "
                    "Please also update the standing of "
                    "your %(contact_category)s accordingly."
                )
                % {
                    "organization_name": organization_name,
                    "contact_category": contact.category,
                    "contact_name": contact.name,
                },
            )
        elif standing_request.is_standing_revocation:
            if standing_request.user:
                notify(
                    user=standing_request.user,
                    title=f"{__title__}: Standing with {contact.name} revoked",
                    message=_(
                        "'%(organization_name)s' no longer has "
                        "standing with your "
                        "%(contact_category)s '%(contact_name)s'. "
                        "Please also update the standing of "
                        "your %(contact_category)s accordingly."
                    )
                    % {
                        "organization_name": organization_name,
                        "contact_category": contact.category,
                        "contact_name": contact.name,
                    },
                )

    def _removing_effective_standing(self, standing_request: AbstractStandingsRequest):
        from .models import StandingRevocation

        logger.info(
            "Standing for %d is marked as effective but is not "
            "satisfied in game. Deleting.",
            standing_request.contact_id,
        )
        standing_request.delete(reason=StandingRevocation.Reason.REVOKED_IN_GAME)

    def _remove_standing_request_after_revocation(self, standing_request):
        from .models import StandingRequest, StandingRevocation

        StandingRequest.objects.filter(contact_id=standing_request.contact_id).delete()
        StandingRevocation.objects.filter(
            contact_id=standing_request.contact_id
        ).delete()

    def _notify_user_about_timed_out_request(
        self,
        standing_request: AbstractStandingsRequest,
        contact: EveEntity,
        actioned_timeout,
    ):
        title = _("Standing Request for %s reset") % contact.name
        message = (
            _(
                "The standing request for %(contact_category)s "
                "'%(contact_name)s' from %(user_name)s "
                "has been reset as it did not appear in "
                "game before the timeout period expired."
            )
            % {
                "contact_category": contact.category,
                "contact_name": contact.name,
                "user_name": standing_request.user.username,
            },
        )

        # Notify standing manager
        notify(user=actioned_timeout, title=title, message=message)
        # Notify the user
        notify(user=standing_request.user, title=title, message=message)

    def has_pending_request(self, contact_id: int) -> bool:
        """Checks if a request is pending for the given contact_id

        contact_id: int contact_id to check the pending request for

        returns True if a request is already pending, False otherwise
        """
        return self.pending_requests().filter(contact_id=contact_id).exists()

    def pending_requests(self) -> models.QuerySet:
        """returns all pending requests for this class"""
        return self.filter(action_date__isnull=True, is_effective=False)


AbstractStandingsRequestManager = _AbstractStandingsRequestManagerBase.from_queryset(
    AbstractStandingsRequestQuerySet
)


class StandingRequestManager(AbstractStandingsRequestManager):
    def validate_requests(self) -> int:
        """Validate all StandingsRequests and check
        that the user requesting them has permission and has API keys
        associated with the character/corp.

        StandingRevocation are created for invalid standing requests

        returns the number of invalid requests
        """
        from .models import StandingRevocation

        logger.debug("Validating standings requests")
        invalid_count = 0
        for standing_request in self.all():
            logger.debug(
                "Checking request for contact_id %d", standing_request.contact_id
            )
            reason = StandingRevocation.Reason.NONE
            if not standing_request.user.has_perm(self.model.REQUEST_PERMISSION_NAME):
                logger.debug("Request is invalid, user does not have permission")
                reason = StandingRevocation.Reason.LOST_PERMISSION
                is_valid = False

            elif (
                standing_request.is_corporation
                and not self.model.can_request_corporation_standing(
                    standing_request.contact_id, standing_request.user
                )
            ):
                logger.debug("Request is invalid, not all corp API keys recorded.")
                reason = StandingRevocation.Reason.MISSING_CORP_TOKEN
                is_valid = False

            else:
                is_valid = True

            if not is_valid:
                logger.info(
                    "Standing request for contact_id %d no longer valid. "
                    "Creating revocation",
                    standing_request.contact_id,
                )
                StandingRevocation.objects.add_revocation(
                    contact_id=standing_request.contact_id,
                    contact_type=self.model.contact_id_2_type(
                        standing_request.contact_type_id
                    ),
                    user=standing_request.user,
                    reason=reason,
                )
                invalid_count += 1

        return invalid_count

    def create_character_request(
        self, user: User, character: EveCharacter
    ) -> CreateCharacterRequestResult:
        """Create new character standings request for user if possible."""
        from .models import ContactSet, RequestLogEntry, StandingRevocation

        try:
            if character.character_ownership.user != user:
                logger.warning(
                    "%s: User %s does not own character, forbidden", character, user
                )
                return CreateCharacterRequestResult.USER_IS_NOT_OWNER

        except ObjectDoesNotExist:
            return CreateCharacterRequestResult.USER_IS_NOT_OWNER

        try:
            contact_set = ContactSet.objects.latest()
        except ContactSet.DoesNotExist:
            logger.warning("Failed to get a contact set")
            return CreateCharacterRequestResult.UNKNOWN_ERROR
        character_id = character.character_id

        if self.has_pending_request(
            character_id
        ) or StandingRevocation.objects.has_pending_request(character_id):
            logger.warning("%s: Character already has a pending request", character)
            return CreateCharacterRequestResult.CHARACTER_HAS_REQUEST

        if not self.model.has_required_scopes_for_request(
            character=character, user=user
        ):
            logger.warning("%s: Character does not have the required scopes", character)
            return CreateCharacterRequestResult.CHARACTER_IS_MISSING_SCOPES

        sr = self.get_or_create_2(
            user=user,
            contact_id=character_id,
            contact_type=self.model.ContactType.CHARACTER,
        )
        if contact_set.contact_has_satisfied_standing(character_id):
            sr.mark_actioned(user=None, reason=sr.Reason.STANDING_IN_GAME)
            sr.mark_effective()
            RequestLogEntry.objects.create_from_standing_request(
                sr, RequestLogEntry.Action.CONFIRMED, None
            )

        return CreateCharacterRequestResult.NO_ERROR

    def create_corporation_request(self, user: User, corporation_id: int) -> bool:
        """Create new corporation standings request for user if possible."""
        from .models import StandingRevocation

        if self.has_pending_request(
            corporation_id
        ) or StandingRevocation.objects.has_pending_request(corporation_id):
            logger.warning(
                "Contact ID %d already has a pending request", corporation_id
            )
            return False
        if not self.model.can_request_corporation_standing(corporation_id, user):
            logger.warning(
                "User %s does not have enough keys for corpID %d, forbidden",
                user,
                corporation_id,
            )
            return False
        self.get_or_create_2(
            user=user,
            contact_id=corporation_id,
            contact_type=self.model.ContactType.CORPORATION,
        )
        return True

    def get_or_create_2(
        self, user: User, contact_id: int, contact_type: str
    ) -> StandingRequest:
        """Get or create a new standing request

        Params:
        - user: User the request and contact_id belongs to
        - contact_id: contact_id to request standings on
        - contact_type: type of this contact

        Returns the created StandingRequest instance
        """
        contact_type_id = self.model.contact_type_2_id(contact_type)
        instance, _ = self.get_or_create(
            contact_id=contact_id,
            contact_type_id=contact_type_id,
            defaults={"user": user},
        )
        return instance


class StandingRevocationManager(AbstractStandingsRequestManager):
    def add_revocation(
        self,
        contact_id: int,
        contact_type: str,
        user: Optional[User] = None,
        reason: Optional[AbstractStandingsRequest.Reason] = None,
    ) -> object:
        """Add a new standings revocation

        Params:
        - contact_id: contact_id to request standings on
        - contact_type_id: contact_type_id from AbstractContact concrete implementation
        - user: user making the request

        Returns the created StandingRevocation instance
        """
        from .models import AbstractStandingsRequest

        logger.debug(
            "Adding new standings revocation for contact %d type %s",
            contact_id,
            contact_type,
        )
        contact_type_id = AbstractStandingsRequest.contact_type_2_id(contact_type)
        if self.has_pending_request(contact_id):
            logger.debug(
                "Cannot add revocation for contact %d %s, pending revocation exists",
                contact_id,
                contact_type_id,
            )
            return None

        if not reason:
            reason = AbstractStandingsRequest.Reason.NONE

        instance = self.create(
            contact_id=contact_id,
            contact_type_id=contact_type_id,
            user=user,
            reason=AbstractStandingsRequest.Reason(reason),
        )
        return instance


class CharacterAffiliationManager(models.Manager):
    def update_evecharacter_relations(self) -> None:
        """Update links to eve character in auth if any"""

        eve_character_id_map = {
            obj["character_id"]: obj["id"]
            for obj in EveCharacter.objects.values("id", "character_id")
        }
        with transaction.atomic():
            affiliations = self.filter(character_id__in=eve_character_id_map.keys())
            for affiliation in affiliations:
                affiliation.eve_character_id = eve_character_id_map[
                    affiliation.character_id
                ]
            self.bulk_update(
                objs=affiliations, fields=["eve_character_id"], batch_size=500
            )

    def update_from_esi(self) -> None:
        """Update all character affiliations we have contacts or requests for."""
        character_ids = self._gather_character_ids()
        if character_ids:
            affiliations = self._fetch_characters_affiliation_from_esi(character_ids)
            if affiliations:
                self._store_affiliations(affiliations)

    def _gather_character_ids(self) -> list:
        from .models import ContactSet, StandingRequest, StandingRevocation

        try:
            contact_set = ContactSet.objects.latest()
        except ContactSet.DoesNotExist:
            logger.warning("Could not find a contact set")
            return []

        character_ids_contacts = set(
            contact_set.contacts.filter_characters()
            .values_list("eve_entity_id", flat=True)
            .distinct()
        )
        character_ids_requests = set(
            StandingRequest.objects.filter_characters()
            .values_list("contact_id", flat=True)
            .distinct()
        )
        character_ids_revocations = set(
            StandingRevocation.objects.filter_characters()
            .values_list("contact_id", flat=True)
            .distinct()
        )
        return list(
            character_ids_contacts | character_ids_requests | character_ids_revocations
        )

    def _fetch_characters_affiliation_from_esi(self, character_ids) -> list:
        chunk_size = 1000
        affiliations = []
        for character_ids_chunk in chunks(character_ids, chunk_size):
            try:
                response = esi.client.Character.post_characters_affiliation(
                    characters=character_ids_chunk
                ).results()
            except HTTPError:
                logger.exception("Could not fetch character affiliations from ESI")
                return []

            affiliations += response

        return affiliations

    def _store_affiliations(self, affiliations) -> None:
        affiliation_objects = []
        for affiliation in affiliations:
            character, _ = EveEntity.objects.get_or_create(
                id=affiliation["character_id"]
            )
            corporation, _ = EveEntity.objects.get_or_create(
                id=affiliation["corporation_id"]
            )

            if affiliation.get("alliance_id"):
                alliance, _ = EveEntity.objects.get_or_create(
                    id=affiliation["alliance_id"]
                )

            else:
                alliance = None

            if affiliation.get("faction_id"):
                faction, _ = EveEntity.objects.get_or_create(
                    id=affiliation["faction_id"]
                )
            else:
                faction = None

            affiliation_objects.append(
                self.model(
                    character=character,
                    corporation=corporation,
                    alliance=alliance,
                    faction=faction,
                )
            )

        with transaction.atomic():
            self.all().delete()
            self.bulk_create(affiliation_objects, batch_size=500)

        new_ids = set()
        for obj in affiliation_objects:
            new_ids |= obj.entity_ids()

        EveEntity.objects.bulk_resolve_ids(new_ids)


class CorporationDetailsManager(models.Manager):
    def corporation_ids_from_contacts(self) -> set:
        from .models import Contact

        contact_corporation_ids = set(
            Contact.objects.filter_corporations().values_list(
                "eve_entity_id", flat=True
            )
        )
        character_affiliation_corporation_ids = set(
            Contact.objects.filter_characters().values_list(
                "eve_entity__character_affiliation__corporation_id", flat=True
            )
        )
        all_ids = contact_corporation_ids | character_affiliation_corporation_ids
        all_ids.discard(None)
        return all_ids

    def update_or_create_from_esi(self, id: int) -> Tuple[Any, bool]:
        """Updates or create an obj from ESI"""
        logger.info("%s: Fetching corporation from ESI", id)
        data = esi.client.Corporation.get_corporations_corporation_id(
            corporation_id=id
        ).results()
        corporation = EveEntity.objects.get_or_create(id=id)[0]
        alliance = (
            EveEntity.objects.get_or_create(id=data["alliance_id"])[0]
            if data.get("alliance_id")
            else None
        )
        ceo_id = data["ceo_id"] if data["ceo_id"] and data["ceo_id"] > 1 else None
        ceo = EveEntity.objects.get_or_create(id=ceo_id)[0] if ceo_id else None
        faction = (
            EveEntity.objects.get_or_create(id=data["faction_id"])[0]
            if data.get("faction_id")
            else None
        )
        EveEntity.objects.bulk_resolve_ids(
            filter(
                lambda x: x is not None,
                [id, data.get("alliance_id"), ceo_id, data.get("faction_id")],
            )
        )
        return self.update_or_create(
            corporation=corporation,
            defaults={
                "alliance": alliance,
                "ceo": ceo,
                "faction": faction,
                "member_count": data["member_count"],
                "ticker": data["ticker"],
            },
        )


class FrozenQuerySetMixin:
    """Ensures the update method can not be used."""

    def update(self, **kwargs) -> int:
        raise RuntimeError("Update not allowed for this model.")


class RequestLogEntryQuerySet(FrozenQuerySetMixin, models.QuerySet):
    pass


class RequestLogEntryManagerBase(models.Manager):
    # TODO: This method should be called as tasks, and entities should be resolved
    def create_from_standing_request(
        self, standing_request: AbstractStandingsRequest, action, action_by: User
    ) -> Optional[Any]:
        from .models import FrozenAlt, FrozenAuthUser, RequestLogEntry

        requested_for: FrozenAlt = (
            FrozenAlt.objects.get_or_create_from_standing_request(standing_request)[0]
        )
        if action_by:
            action_by_obj: Optional[FrozenAuthUser] = (
                FrozenAuthUser.objects.get_or_create_from_user(action_by)[0]
            )
        else:
            action_by_obj = None

        requested_by_obj: FrozenAuthUser = (
            FrozenAuthUser.objects.get_or_create_from_user(standing_request.user)[0]
        )
        request_type = RequestLogEntry.RequestType.from_standing_request(
            standing_request
        )
        new_obj = self.create(
            action=RequestLogEntry.Action(action),
            action_by=action_by_obj,
            request_type=request_type,
            requested_at=standing_request.request_date,
            requested_by=requested_by_obj,
            requested_for=requested_for,
            reason=standing_request.reason,
        )
        eve_entity_ids = requested_for.entity_ids() | requested_by_obj.entity_ids()
        if action_by_obj:
            eve_entity_ids |= action_by_obj.entity_ids()

        create_eve_entities.delay(list(eve_entity_ids))
        return new_obj


RequestLogEntryManager = RequestLogEntryManagerBase.from_queryset(
    RequestLogEntryQuerySet
)


class FrozenAuthUserQuerySet(FrozenQuerySetMixin, models.QuerySet):
    pass


class FrozenAuthUserManagerBase(models.Manager):
    def get_or_create_from_user(self, user: User) -> Tuple[Any, bool]:
        main_character = user.profile.main_character
        if main_character:
            character_id = main_character.character_id
            corporation_id = main_character.corporation_id
            alliance_id = main_character.alliance_id
            faction_id = main_character.faction_id

        else:
            character_id = corporation_id = alliance_id = faction_id = None

        try:
            obj = self.get(
                user_id=user.id,
                character_id=character_id,
                corporation_id=corporation_id,
                alliance_id=alliance_id,
                faction_id=faction_id,
            )
            return obj, False

        except self.model.DoesNotExist:
            alliance = (
                EveEntity.objects.get_or_create(id=alliance_id)[0]
                if alliance_id
                else None
            )
            character = (
                EveEntity.objects.get_or_create(id=character_id)[0]
                if character_id
                else None
            )
            corporation = (
                EveEntity.objects.get_or_create(id=corporation_id)[0]
                if corporation_id
                else None
            )
            faction = (
                EveEntity.objects.get_or_create(id=faction_id)[0]
                if faction_id
                else None
            )
            return self.get_or_create(
                user=user,
                character=character,
                corporation=corporation,
                alliance=alliance,
                faction=faction,
                state=user.profile.state,
            )


FrozenAuthUserManager = FrozenAuthUserManagerBase.from_queryset(FrozenAuthUserQuerySet)


class FrozenAltQuerySet(FrozenQuerySetMixin, models.QuerySet):
    pass


class FrozenAltManagerBase(models.Manager):
    def get_or_create_from_standing_request(
        self, standing_request: AbstractStandingsRequest
    ) -> Tuple[Any, bool]:
        eve_entity, _ = EveEntity.objects.get_or_create(id=standing_request.contact_id)

        if standing_request.is_character:
            category = self.model.Category.CHARACTER
            character = eve_entity
            try:
                alliance = character.character_affiliation.alliance
                corporation = character.character_affiliation.corporation
                faction = character.character_affiliation.faction
            except ObjectDoesNotExist:
                alliance = None
                corporation = None
                faction = None

        elif standing_request.is_corporation:
            category = self.model.Category.CORPORATION
            character = None
            corporation = eve_entity
            try:
                alliance = corporation.corporation_details.alliance
                faction = corporation.corporation_details.faction
            except ObjectDoesNotExist:
                alliance = None
                faction = None

        else:
            raise NotImplementedError()

        return self.get_or_create(
            character=character,
            corporation=corporation,
            alliance=alliance,
            category=category,
            faction=faction,
        )


FrozenAltManager = FrozenAltManagerBase.from_queryset(FrozenAltQuerySet)
