from django.db import IntegrityError, transaction

from careers.models import CareerStatus
from players.models import Player

from .constants import NUTRITION_POLICY_KEY, NUTRITION_POLICY_VERSION
from .exceptions import (
    NutritionIntegrityError,
    NutritionPlayerInactiveError,
    NutritionPlayerNotFoundError,
)
from .models import NutritionSelectionEvent, PlayerNutritionProfile
from .policy import NutritionPolicyV1
from .validators import validate_nutrition_level


class NutritionProfileService:
    @staticmethod
    @transaction.atomic
    def select_level(*, player_id, level) -> PlayerNutritionProfile:
        level = validate_nutrition_level(level)
        player = _lock_player(player_id)
        _validate_active_player(player)

        profile = (
            PlayerNutritionProfile.objects
            .select_for_update()
            .filter(player=player)
            .first()
        )
        policy = NutritionPolicyV1.resolve(level)

        if profile is not None and (
            profile.level == level
            and profile.policy_key == NUTRITION_POLICY_KEY
            and profile.policy_version == NUTRITION_POLICY_VERSION
        ):
            return profile

        if profile is None:
            previous_level = None
            revision = 1
            try:
                profile = PlayerNutritionProfile.objects.create(
                    player=player,
                    level=level,
                    revision=revision,
                    policy_key=NUTRITION_POLICY_KEY,
                    policy_version=NUTRITION_POLICY_VERSION,
                )
            except IntegrityError as exc:
                raise NutritionIntegrityError(
                    "Nutrition profile violates a database invariant."
                ) from exc
        else:
            previous_level = profile.level
            revision = profile.revision + 1
            profile.level = level
            profile.revision = revision
            profile.policy_key = NUTRITION_POLICY_KEY
            profile.policy_version = NUTRITION_POLICY_VERSION
            profile.save(
                update_fields=[
                    "level",
                    "revision",
                    "policy_key",
                    "policy_version",
                    "updated_at",
                ]
            )

        try:
            NutritionSelectionEvent.objects.create(
                player=player,
                revision=revision,
                previous_level=previous_level,
                new_level=level,
                multiplier_snapshot=policy.multiplier,
                policy_key=policy.policy_key,
                policy_version=policy.policy_version,
            )
        except IntegrityError as exc:
            raise NutritionIntegrityError(
                "Nutrition selection history violates a database invariant."
            ) from exc
        return profile



def _lock_player(player_id):
    try:
        return (
            Player.objects.select_for_update()
            .select_related("career")
            .get(pk=player_id)
        )
    except Player.DoesNotExist as exc:
        raise NutritionPlayerNotFoundError("Player does not exist.") from exc



def _validate_active_player(player):
    if player.career.status != CareerStatus.ACTIVE:
        raise NutritionPlayerInactiveError(
            "Only a player with an ACTIVE career can change nutrition level."
        )
