import uuid

from django.conf import settings
from django.db import models
from django.db.models import F, Q

from .constants import (
    TRAINABLE_PLAYER_ATTRIBUTES,
    TRAINING_POLICY_KEY,
    TRAINING_POLICY_VERSION,
)
from .exceptions import TrainingResultImmutableError


class TrainingSessionType(models.TextChoices):
    INDIVIDUAL = "INDIVIDUAL", "Individual"
    TEAM = "TEAM", "Team"


class TrainingSessionStatus(models.TextChoices):
    SCHEDULED = "SCHEDULED", "Scheduled"
    COMPLETED = "COMPLETED", "Completed"
    CANCELLED = "CANCELLED", "Cancelled"


class TrainingSession(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    session_type = models.CharField(max_length=20, choices=TrainingSessionType.choices)
    status = models.CharField(
        max_length=20,
        choices=TrainingSessionStatus.choices,
        default=TrainingSessionStatus.SCHEDULED,
    )
    training_date = models.DateField()
    starts_at = models.DateTimeField(null=True, blank=True)
    ends_at = models.DateTimeField(null=True, blank=True)
    slot_number = models.PositiveSmallIntegerField(null=True, blank=True)
    formation = models.CharField(max_length=24, blank=True, default="")
    formation_slots = models.JSONField(default=list, blank=True)
    formation_key = models.CharField(max_length=100, blank=True, default="")
    club = models.ForeignKey(
        "clubs.Club",
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name="training_sessions",
    )
    target_attribute = models.CharField(max_length=40)
    base_effect = models.DecimalField(max_digits=8, decimal_places=6, default="1.000000")
    policy_key = models.CharField(max_length=100, default=TRAINING_POLICY_KEY)
    policy_version = models.CharField(max_length=30, default=TRAINING_POLICY_VERSION)
    created_at = models.DateTimeField(auto_now_add=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    cancelled_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["training_date", "created_at", "id"]
        indexes = [
            models.Index(fields=["training_date", "status"], name="training_date_status_idx"),
            models.Index(fields=["club", "training_date"], name="training_club_date_idx"),
        ]
        constraints = [
            models.UniqueConstraint(fields=["club", "training_date", "slot_number"], condition=Q(status="SCHEDULED", slot_number__isnull=False), name="training_active_team_slot"),
            models.CheckConstraint(condition=Q(slot_number__isnull=True, starts_at__isnull=True, ends_at__isnull=True) | Q(session_type="TEAM", slot_number__in=[1,2], starts_at__isnull=False, ends_at__isnull=False, ends_at__gt=F("starts_at")), name="training_timed_slot_valid"),
            models.CheckConstraint(
                condition=Q(session_type__in=TrainingSessionType.values),
                name="training_valid_session_type",
            ),
            models.CheckConstraint(
                condition=Q(status__in=TrainingSessionStatus.values),
                name="training_valid_status",
            ),
            models.CheckConstraint(
                condition=Q(target_attribute__in=TRAINABLE_PLAYER_ATTRIBUTES),
                name="training_valid_attribute",
            ),
            models.CheckConstraint(
                condition=Q(base_effect__gt=0),
                name="training_base_effect_positive",
            ),
            models.CheckConstraint(
                condition=(
                    Q(session_type=TrainingSessionType.INDIVIDUAL, club__isnull=True)
                    | Q(session_type=TrainingSessionType.TEAM, club__isnull=False)
                ),
                name="training_type_club_consistent",
            ),
            models.CheckConstraint(
                condition=(
                    Q(
                        status=TrainingSessionStatus.SCHEDULED,
                        completed_at__isnull=True,
                        cancelled_at__isnull=True,
                    )
                    | Q(
                        status=TrainingSessionStatus.COMPLETED,
                        completed_at__isnull=False,
                        cancelled_at__isnull=True,
                    )
                    | Q(
                        status=TrainingSessionStatus.CANCELLED,
                        completed_at__isnull=True,
                        cancelled_at__isnull=False,
                    )
                ),
                name="training_session_lifecycle_consistent",
            ),
        ]


class TrainingParticipant(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    session = models.ForeignKey(
        TrainingSession,
        on_delete=models.PROTECT,
        related_name="participants",
    )
    player = models.ForeignKey(
        "players.Player",
        on_delete=models.PROTECT,
        related_name="training_participations",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    checked_in_at = models.DateTimeField(null=True, blank=True)
    evidence = models.JSONField(default=dict, blank=True)

    class Meta:
        ordering = ["session_id", "player_id"]
        constraints = [
            models.UniqueConstraint(
                fields=["session", "player"],
                name="uq_training_session_player",
            )
        ]
        indexes = [
            models.Index(fields=["player", "session"], name="training_player_session_idx"),
        ]


class PlayerDevelopmentProgress(models.Model):
    """Mutable accumulated development credits for one Player attribute.

    These are intentionally *not* player rating points.  A future versioned
    materialization policy will decide how accumulated credits change actual
    ratings and recalculate overall without silently baking a balancing choice
    into the Training foundation.
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    player = models.ForeignKey(
        "players.Player",
        on_delete=models.PROTECT,
        related_name="development_progress",
    )
    attribute_name = models.CharField(max_length=40)
    total_points = models.DecimalField(max_digits=18, decimal_places=6, default="0.000000")
    consumed_points = models.DecimalField(max_digits=18, decimal_places=6, default="0.000000")
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["player_id", "attribute_name"]
        constraints = [
            models.UniqueConstraint(
                fields=["player", "attribute_name"],
                name="uq_player_development_attribute",
            ),
            models.CheckConstraint(
                condition=Q(attribute_name__in=TRAINABLE_PLAYER_ATTRIBUTES),
                name="development_valid_attribute",
            ),
            models.CheckConstraint(
                condition=Q(total_points__gte=0, consumed_points__gte=0),
                name="development_points_nonnegative",
            ),
            models.CheckConstraint(
                condition=Q(consumed_points__lte=F("total_points")),
                name="development_consumed_lte_total",
            ),
        ]


class _ImmutableTrainingResultQuerySet(models.QuerySet):
    def update(self, **kwargs):
        raise TrainingResultImmutableError("Training development results are immutable.")

    def delete(self):
        raise TrainingResultImmutableError("Training development results are immutable.")


class TrainingDevelopmentResult(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    participant = models.ForeignKey(
        TrainingParticipant,
        on_delete=models.PROTECT,
        related_name="development_results",
    )
    player = models.ForeignKey(
        "players.Player",
        on_delete=models.PROTECT,
        related_name="training_development_results",
    )
    attribute_name = models.CharField(max_length=40)
    training_date = models.DateField()
    age_years = models.PositiveSmallIntegerField()
    overall_before = models.PositiveSmallIntegerField()
    potential_at_training = models.PositiveSmallIntegerField()
    attribute_rating_before = models.PositiveSmallIntegerField()

    base_effect = models.DecimalField(max_digits=12, decimal_places=6)
    potential_factor = models.DecimalField(max_digits=12, decimal_places=6)
    age_factor = models.DecimalField(max_digits=12, decimal_places=6)
    nutrition_multiplier = models.DecimalField(max_digits=12, decimal_places=6)
    coach_multiplier = models.DecimalField(max_digits=12, decimal_places=6)
    fatigue_multiplier = models.DecimalField(max_digits=12, decimal_places=6)
    injury_multiplier = models.DecimalField(max_digits=12, decimal_places=6)
    final_effect = models.DecimalField(max_digits=18, decimal_places=6)

    progress_before = models.DecimalField(max_digits=18, decimal_places=6)
    progress_after = models.DecimalField(max_digits=18, decimal_places=6)
    policy_key = models.CharField(max_length=100)
    policy_version = models.CharField(max_length=30)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableTrainingResultQuerySet.as_manager()

    class Meta:
        ordering = ["training_date", "created_at", "id"]
        indexes = [
            models.Index(fields=["player", "training_date"], name="trainres_player_date_idx"),
        ]
        constraints = [
            models.UniqueConstraint(fields=["participant", "attribute_name"], name="training_result_part_attribute"),
            models.CheckConstraint(
                condition=Q(attribute_name__in=TRAINABLE_PLAYER_ATTRIBUTES),
                name="training_result_valid_attribute",
            ),
            models.CheckConstraint(
                condition=Q(age_years__lte=100),
                name="training_result_age_lte_100",
            ),
            models.CheckConstraint(
                condition=Q(overall_before__gte=1, overall_before__lte=100),
                name="training_result_overall_1_100",
            ),
            models.CheckConstraint(
                condition=Q(potential_at_training__gte=1, potential_at_training__lte=100),
                name="training_result_potential_1_100",
            ),
            models.CheckConstraint(
                condition=Q(attribute_rating_before__gte=1, attribute_rating_before__lte=100),
                name="training_result_rating_1_100",
            ),
            models.CheckConstraint(
                condition=Q(final_effect__gte=0),
                name="training_result_effect_nonnegative",
            ),
            models.CheckConstraint(
                condition=Q(progress_before__gte=0, progress_after__gte=F("progress_before")),
                name="training_result_progress_monotonic",
            ),
            models.CheckConstraint(
                condition=Q(progress_after=F("progress_before") + F("final_effect")),
                name="training_result_progress_math",
            ),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise TrainingResultImmutableError("Training development results are immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise TrainingResultImmutableError("Training development results are immutable.")


class MatchExperienceDevelopmentResult(models.Model):
    """Immutable attribution of match-experience credit into development progress."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    dynamic_effect = models.ForeignKey(
        "players.PlayerMatchDynamicEffect",
        on_delete=models.PROTECT,
        related_name="development_results",
    )
    player = models.ForeignKey(
        "players.Player",
        on_delete=models.PROTECT,
        related_name="match_experience_development_results",
    )
    attribute_name = models.CharField(max_length=40)
    credit = models.DecimalField(max_digits=18, decimal_places=6)
    progress_before = models.DecimalField(max_digits=18, decimal_places=6)
    progress_after = models.DecimalField(max_digits=18, decimal_places=6)
    policy_key = models.CharField(max_length=100)
    policy_version = models.CharField(max_length=30)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableTrainingResultQuerySet.as_manager()

    class Meta:
        ordering = ["player_id", "created_at", "attribute_name"]
        constraints = [
            models.UniqueConstraint(
                fields=["dynamic_effect", "attribute_name"],
                name="uq_match_exp_effect_attribute",
            ),
            models.CheckConstraint(
                condition=Q(attribute_name__in=TRAINABLE_PLAYER_ATTRIBUTES),
                name="match_exp_valid_attribute",
            ),
            models.CheckConstraint(
                condition=Q(credit__gte=0),
                name="match_exp_credit_nonnegative",
            ),
            models.CheckConstraint(
                condition=Q(progress_before__gte=0, progress_after__gte=F("progress_before")),
                name="match_exp_progress_monotonic",
            ),
            models.CheckConstraint(
                condition=Q(progress_after=F("progress_before") + F("credit")),
                name="match_exp_progress_math",
            ),
            models.CheckConstraint(condition=~Q(policy_key=""), name="match_exp_policy_key_nonempty"),
            models.CheckConstraint(condition=~Q(policy_version=""), name="match_exp_policy_ver_nonempty"),
        ]
        indexes = [
            models.Index(fields=["player", "created_at"], name="match_exp_player_date_idx"),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise TrainingResultImmutableError("Match experience development results are immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise TrainingResultImmutableError("Match experience development results are immutable.")


class PlayerRatingMaterializationTrigger(models.TextChoices):
    TRAINING = "TRAINING", "Training session"
    MATCH = "MATCH", "Match experience"
    MANUAL = "MANUAL", "Manual materialization"
    SCHEDULED = "SCHEDULED", "Scheduled materialization"
    SEASON_CREDIT = "SEASON_CREDIT", "Season-end credit sweep"
    SEASON_END = "SEASON_END", "Season-end aging"


class _ImmutableProgressionQuerySet(models.QuerySet):
    def update(self, **kwargs):
        raise TrainingResultImmutableError("Player progression evidence is immutable.")

    def delete(self):
        raise TrainingResultImmutableError("Player progression evidence is immutable.")


class PlayerRatingMaterializationRun(models.Model):
    """Immutable evidence for one conversion of development credit into ratings."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    player = models.ForeignKey("players.Player", on_delete=models.PROTECT, related_name="rating_materialization_runs")
    trigger_type = models.CharField(max_length=20, choices=PlayerRatingMaterializationTrigger.choices)
    source_reference = models.UUIDField(null=True, blank=True)
    observed_on = models.DateField()
    age_years = models.PositiveSmallIntegerField()
    overall_before = models.PositiveSmallIntegerField()
    overall_after = models.PositiveSmallIntegerField()
    potential = models.PositiveSmallIntegerField()
    total_credit_consumed = models.DecimalField(max_digits=18, decimal_places=6, default="0.000000")
    retirement_eligible = models.BooleanField(default=False)
    evidence_payload = models.JSONField()
    evidence_hash = models.CharField(max_length=64)
    policy_key = models.CharField(max_length=100)
    policy_version = models.CharField(max_length=30)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableProgressionQuerySet.as_manager()

    class Meta:
        ordering = ["created_at", "player_id"]
        constraints = [
            models.UniqueConstraint(fields=["player", "trigger_type", "source_reference"], condition=Q(source_reference__isnull=False), name="uq_ratingmat_player_trigger_ref"),
            models.CheckConstraint(condition=Q(age_years__lte=100), name="ratingmat_age_lte_100"),
            models.CheckConstraint(condition=Q(overall_before__gte=1, overall_before__lte=100, overall_after__gte=1, overall_after__lte=100), name="ratingmat_overall_1_100"),
            models.CheckConstraint(condition=Q(potential__gte=1, potential__lte=100), name="ratingmat_potential_1_100"),
            models.CheckConstraint(condition=Q(overall_after__lte=F("potential")), name="ratingmat_overall_lte_potential"),
            models.CheckConstraint(condition=Q(total_credit_consumed__gte=0), name="ratingmat_credit_nonnegative"),
            models.CheckConstraint(condition=~Q(evidence_hash=""), name="ratingmat_hash_nonempty"),
            models.CheckConstraint(condition=~Q(policy_key=""), name="ratingmat_policy_nonempty"),
        ]
        indexes = [
            models.Index(fields=["player", "-created_at"], name="ratingmat_player_hist_idx"),
            models.Index(fields=["trigger_type", "observed_on"], name="ratingmat_trigger_date_idx"),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise TrainingResultImmutableError("Player progression evidence is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise TrainingResultImmutableError("Player progression evidence is immutable.")


class PlayerAttributeRatingChange(models.Model):
    """Immutable per-attribute delta belonging to a materialization/aging run."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    run = models.ForeignKey(PlayerRatingMaterializationRun, on_delete=models.PROTECT, related_name="attribute_changes")
    attribute_name = models.CharField(max_length=40)
    rating_before = models.PositiveSmallIntegerField()
    rating_after = models.PositiveSmallIntegerField()
    rating_delta = models.SmallIntegerField()
    credit_available_before = models.DecimalField(max_digits=18, decimal_places=6, default="0.000000")
    credit_consumed = models.DecimalField(max_digits=18, decimal_places=6, default="0.000000")
    credit_available_after = models.DecimalField(max_digits=18, decimal_places=6, default="0.000000")
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableProgressionQuerySet.as_manager()

    class Meta:
        ordering = ["run_id", "attribute_name"]
        constraints = [
            models.UniqueConstraint(fields=["run", "attribute_name"], name="uq_ratingchange_run_attribute"),
            models.CheckConstraint(condition=Q(attribute_name__in=TRAINABLE_PLAYER_ATTRIBUTES), name="ratingchange_valid_attribute"),
            models.CheckConstraint(condition=Q(rating_before__gte=1, rating_before__lte=100, rating_after__gte=1, rating_after__lte=100), name="ratingchange_rating_1_100"),
            models.CheckConstraint(condition=Q(rating_delta=F("rating_after") - F("rating_before")), name="ratingchange_delta_math"),
            models.CheckConstraint(condition=Q(credit_available_before__gte=0, credit_consumed__gte=0, credit_available_after__gte=0), name="ratingchange_credit_nonnegative"),
            models.CheckConstraint(condition=Q(credit_consumed__lte=F("credit_available_before")), name="ratingchange_consumed_lte_available"),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise TrainingResultImmutableError("Player progression evidence is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise TrainingResultImmutableError("Player progression evidence is immutable.")


class PlayerSeasonAgingResult(models.Model):
    """One idempotent aging/decline decision per player and completed season."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    player = models.ForeignKey("players.Player", on_delete=models.PROTECT, related_name="season_aging_results")
    season = models.ForeignKey("seasons.Season", on_delete=models.PROTECT, related_name="player_aging_results")
    materialization_run = models.OneToOneField(PlayerRatingMaterializationRun, on_delete=models.PROTECT, related_name="aging_result")
    age_years = models.PositiveSmallIntegerField()
    base_decline_points = models.PositiveSmallIntegerField(default=0)
    applied_decline_points = models.PositiveSmallIntegerField(default=0)
    training_protection = models.DecimalField(max_digits=6, decimal_places=4, default="0.0000")
    retirement_eligible = models.BooleanField(default=False)
    evidence_payload = models.JSONField()
    evidence_hash = models.CharField(max_length=64)
    policy_key = models.CharField(max_length=100)
    policy_version = models.CharField(max_length=30)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableProgressionQuerySet.as_manager()

    class Meta:
        ordering = ["season_id", "player_id"]
        constraints = [
            models.UniqueConstraint(fields=["player", "season"], name="uq_player_aging_season"),
            models.CheckConstraint(condition=Q(age_years__lte=100), name="aging_age_lte_100"),
            models.CheckConstraint(condition=Q(applied_decline_points__lte=F("base_decline_points")), name="aging_applied_lte_base"),
            models.CheckConstraint(condition=Q(training_protection__gte=0, training_protection__lte=1), name="aging_protection_0_1"),
            models.CheckConstraint(condition=~Q(evidence_hash=""), name="aging_hash_nonempty"),
        ]
        indexes = [models.Index(fields=["season", "retirement_eligible"], name="aging_season_retire_idx")]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise TrainingResultImmutableError("Player progression evidence is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise TrainingResultImmutableError("Player progression evidence is immutable.")


class PlayerLegacyAssetType(models.TextChoices):
    TRAINING_FACILITY = "TRAINING_FACILITY", "Dedicated training facility"
    TRAINING_EQUIPMENT = "TRAINING_EQUIPMENT", "Special training equipment"
    HIGH_QUALITY_CARD_OPTION = "HIGH_QUALITY_CARD_OPTION", "High-quality new-card option"
    RESTAURANT = "RESTAURANT", "Restaurant and recovery utility"


class PlayerLegacyAssetAssignmentType(models.TextChoices):
    INITIAL_ASSIGNMENT = "INITIAL_ASSIGNMENT", "Initial assignment"
    RETIREMENT_RELEASE = "RETIREMENT_RELEASE", "Released on retirement"
    SUCCESSOR_INHERITANCE = "SUCCESSOR_INHERITANCE", "Inherited by successor"
    OWNER_ASSIGNMENT = "OWNER_ASSIGNMENT", "Owner assignment"
    OWNER_RELEASE = "OWNER_RELEASE", "Owner release"
    LEASE_ASSIGNMENT = "LEASE_ASSIGNMENT", "Lease assignment"
    LEASE_RELEASE = "LEASE_RELEASE", "Lease release"


class PlayerLegacyAsset(models.Model):
    """Account-owned legacy entitlement that may exist with no active player."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="player_legacy_assets"
    )
    origin_player = models.ForeignKey(
        "players.Player", on_delete=models.PROTECT, related_name="originated_legacy_assets"
    )
    assigned_player = models.ForeignKey(
        "players.Player", null=True, blank=True, on_delete=models.PROTECT, related_name="assigned_legacy_assets"
    )
    asset_type = models.CharField(max_length=40, choices=PlayerLegacyAssetType.choices)
    level = models.PositiveSmallIntegerField(default=1)
    metadata = models.JSONField(default=dict, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["owner_id", "asset_type", "created_at", "id"]
        constraints = [
            models.CheckConstraint(condition=Q(asset_type__in=PlayerLegacyAssetType.values), name="legacyasset_valid_type"),
            models.CheckConstraint(condition=Q(level__gte=1, level__lte=10), name="legacyasset_level_1_10"),
            models.UniqueConstraint(
                fields=["assigned_player"],
                condition=Q(asset_type=PlayerLegacyAssetType.TRAINING_FACILITY, assigned_player__isnull=False),
                name="uq_player_one_training_facility",
            ),
        ]
        indexes = [
            models.Index(fields=["owner", "asset_type"], name="legacyasset_owner_type_idx"),
            models.Index(fields=["assigned_player", "asset_type"], name="legacyasset_player_type_idx"),
        ]


class PlayerLegacyAssetAssignment(models.Model):
    """Immutable audit event for legacy-asset custody changes."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    asset = models.ForeignKey(
        PlayerLegacyAsset, on_delete=models.PROTECT, related_name="assignment_events"
    )
    retirement_case = models.ForeignKey(
        "careers.PlayerRetirementCase", null=True, blank=True, on_delete=models.PROTECT,
        related_name="asset_assignment_events"
    )
    event_type = models.CharField(max_length=32, choices=PlayerLegacyAssetAssignmentType.choices)
    from_player = models.ForeignKey(
        "players.Player", null=True, blank=True, on_delete=models.PROTECT,
        related_name="legacy_asset_assignments_from"
    )
    to_player = models.ForeignKey(
        "players.Player", null=True, blank=True, on_delete=models.PROTECT,
        related_name="legacy_asset_assignments_to"
    )
    evidence_payload = models.JSONField()
    evidence_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableProgressionQuerySet.as_manager()

    class Meta:
        ordering = ["created_at", "id"]
        constraints = [
            models.CheckConstraint(condition=Q(event_type__in=PlayerLegacyAssetAssignmentType.values), name="legacyassign_valid_type"),
            models.CheckConstraint(condition=~Q(evidence_hash=""), name="legacyassign_hash_nonempty"),
            models.CheckConstraint(
                condition=(
                    Q(event_type=PlayerLegacyAssetAssignmentType.INITIAL_ASSIGNMENT, from_player__isnull=True, to_player__isnull=False, retirement_case__isnull=True)
                    | Q(event_type=PlayerLegacyAssetAssignmentType.RETIREMENT_RELEASE, from_player__isnull=False, to_player__isnull=True, retirement_case__isnull=False)
                    | Q(event_type=PlayerLegacyAssetAssignmentType.SUCCESSOR_INHERITANCE, from_player__isnull=False, to_player__isnull=False, retirement_case__isnull=False)
                    | Q(event_type=PlayerLegacyAssetAssignmentType.OWNER_ASSIGNMENT, to_player__isnull=False, retirement_case__isnull=True)
                    | Q(event_type=PlayerLegacyAssetAssignmentType.OWNER_RELEASE, from_player__isnull=False, to_player__isnull=True, retirement_case__isnull=True)
                    | Q(event_type=PlayerLegacyAssetAssignmentType.LEASE_ASSIGNMENT, to_player__isnull=False, retirement_case__isnull=True)
                    | Q(event_type=PlayerLegacyAssetAssignmentType.LEASE_RELEASE, from_player__isnull=False, to_player__isnull=True, retirement_case__isnull=True)
                ),
                name="legacyassign_shape_consistent",
            ),
        ]
        indexes = [
            models.Index(fields=["asset", "created_at"], name="legacyassign_asset_time_idx"),
            models.Index(fields=["retirement_case", "event_type"], name="legacyassign_case_type_idx"),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise TrainingResultImmutableError("Legacy asset assignment evidence is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise TrainingResultImmutableError("Legacy asset assignment evidence is immutable.")


class FormationTrainingProgress(models.Model):
    """Only attending players learn a specific club/formation; transfers do not carry it."""
    club = models.ForeignKey("clubs.Club", on_delete=models.PROTECT)
    player = models.ForeignKey("players.Player", on_delete=models.PROTECT)
    formation_key = models.CharField(max_length=100)
    points = models.DecimalField(max_digits=9, decimal_places=6, default="0")
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["club", "player", "formation_key"], name="training_player_club_formation"),
            models.CheckConstraint(condition=Q(points__gte=0, points__lte=100), name="training_formation_points_range"),
        ]
