from datetime import timedelta

from django.db import IntegrityError, transaction
from django.utils import timezone

from seasons.models import Season
from contracts.constants import ContractRosterScope

from .constants import TransferStatus, TransferWindowStatus
from .eligibility import EligibilityStatus, TransferEligibilityService
from .eligibility.context import TransferEligibilityContext
from .exceptions import (
    InvalidTransferWindowStatusError,
    TransferAlreadyExistsError,
    TransferAlreadyProcessedError,
    TransferCompletionContractError,
    TransferCompletionFinancialError,
    TransferInsufficientFundsError,
    TransferWalletUnavailableError,
    TransferDeniedError,
    TransferInvalidStatusError,
    TransferNotApprovableError,
    TransferNotCancellableError,
    TransferNotFoundError,
    TransferNotRejectableError,
    TransferWindowAlreadyCancelledError,
    TransferWindowAlreadyClosedError,
    TransferWindowAlreadyOpenError,
    TransferWindowCannotOpenError,
    TransferWindowNameAlreadyExistsError,
    TransferWindowNotFoundError,
    TransferWindowOverlapError,
)
from .models import Transfer, TransferWindow
from .validators import (
    validate_international_flag,
    validate_rejection_reason,
    validate_transfer_fee,
    validate_transfer_window_dates,
    validate_transfer_window_name,
    validate_transfer_window_type,
)


def _emit_transfer_event(transfer, event_type, payload=None, suffix=""):
    from core_runtime.services import OutboxService
    return OutboxService.enqueue(
        topic="transfers", event_type=event_type, aggregate_type="Transfer", aggregate_id=transfer.pk,
        deduplication_key=f"transfer:{transfer.pk}:{event_type}:{suffix or transfer.status}",
        payload={"transfer_id": str(transfer.pk), "status": transfer.status, **(payload or {})},
    )


class TransferWindowService:
    @staticmethod
    @transaction.atomic
    def create_window(*, season, name, window_type, start_at, end_at):
        name = validate_transfer_window_name(name)
        window_type = validate_transfer_window_type(window_type)
        validate_transfer_window_dates(start_at=start_at, end_at=end_at)

        if season is None or not getattr(season, "pk", None):
            raise TransferWindowNotFoundError("Season must be persisted.")

        try:
            locked_season = Season.objects.select_for_update().get(pk=season.pk)
        except Season.DoesNotExist as exc:
            raise TransferWindowNotFoundError("Season does not exist.") from exc

        # Locking the season serializes window creation through this service,
        # preventing two concurrent overlapping windows from both passing the
        # pre-create overlap check.
        if TransferWindow.objects.filter(
            season=locked_season,
            start_at__lt=end_at,
            end_at__gt=start_at,
        ).exists():
            raise TransferWindowOverlapError(
                "Transfer window overlaps another window in the same season."
            )

        if TransferWindow.objects.filter(
            season=locked_season,
            name=name,
        ).exists():
            raise TransferWindowNameAlreadyExistsError(
                "A transfer window with this name already exists in the season."
            )

        try:
            with transaction.atomic():
                return TransferWindow.objects.create(
                    season=locked_season,
                    name=name,
                    window_type=window_type,
                    start_at=start_at,
                    end_at=end_at,
                    status=TransferWindowStatus.SCHEDULED,
                )
        except IntegrityError as exc:
            raise TransferWindowNameAlreadyExistsError(
                "Transfer window violates a database uniqueness rule."
            ) from exc

    @staticmethod
    @transaction.atomic
    def open_window(*, window_id):
        window = TransferWindowService._lock_window(window_id)

        if window.status == TransferWindowStatus.OPEN:
            raise TransferWindowAlreadyOpenError(
                "Transfer window is already open."
            )
        if window.status != TransferWindowStatus.SCHEDULED:
            raise TransferWindowCannotOpenError(
                "Only a SCHEDULED transfer window can be opened."
            )

        now = timezone.now()
        if now < window.start_at:
            raise TransferWindowCannotOpenError(
                "Transfer window has not started yet."
            )
        if now >= window.end_at:
            raise TransferWindowCannotOpenError(
                "Transfer window has already ended."
            )

        window.status = TransferWindowStatus.OPEN
        window.opened_at = now
        window.closed_at = None
        window.cancelled_at = None
        window.save(
            update_fields=[
                "status",
                "opened_at",
                "closed_at",
                "cancelled_at",
                "updated_at",
            ]
        )
        return window

    @staticmethod
    @transaction.atomic
    def close_window(*, window_id):
        window = TransferWindowService._lock_window(window_id)

        if window.status == TransferWindowStatus.CLOSED:
            raise TransferWindowAlreadyClosedError(
                "Transfer window is already closed."
            )
        if window.status != TransferWindowStatus.OPEN:
            raise InvalidTransferWindowStatusError(
                "Only an OPEN transfer window can be closed."
            )

        window.status = TransferWindowStatus.CLOSED
        window.closed_at = timezone.now()
        window.cancelled_at = None
        window.save(
            update_fields=["status", "closed_at", "cancelled_at", "updated_at"]
        )
        return window

    @staticmethod
    @transaction.atomic
    def cancel_window(*, window_id):
        window = TransferWindowService._lock_window(window_id)

        if window.status == TransferWindowStatus.CANCELLED:
            raise TransferWindowAlreadyCancelledError(
                "Transfer window is already cancelled."
            )
        if window.status != TransferWindowStatus.SCHEDULED:
            raise InvalidTransferWindowStatusError(
                "Only a SCHEDULED transfer window can be cancelled."
            )

        window.status = TransferWindowStatus.CANCELLED
        window.cancelled_at = timezone.now()
        window.opened_at = None
        window.closed_at = None
        window.save(
            update_fields=[
                "status",
                "cancelled_at",
                "opened_at",
                "closed_at",
                "updated_at",
            ]
        )
        return window

    @staticmethod
    def _lock_window(window_id):
        try:
            return (
                TransferWindow.objects
                .select_for_update()
                .select_related("season")
                .get(pk=window_id)
            )
        except TransferWindow.DoesNotExist as exc:
            raise TransferWindowNotFoundError(
                "Transfer window does not exist."
            ) from exc


class TransferService:
    @staticmethod
    @transaction.atomic
    def create_transfer(
        *,
        player,
        source_club=None,
        destination_club,
        window,
        is_international=False,
        transfer_fee=0,
        contract_terms=None,
        now=None,
    ):
        now = now or timezone.now()
        if timezone.is_naive(now):
            now = timezone.make_aware(now)

        if contract_terms is not None:
            from .negotiation import normalize_terms
            contract_terms = normalize_terms(contract_terms)
        transfer_fee = validate_transfer_fee(transfer_fee)
        validate_international_flag(is_international)

        player, source_club, destination_club, window = _lock_transfer_context(
            player=player,
            source_club=source_club,
            destination_club=destination_club,
            window=window,
        )
        from club_licensing.services import ClubSanctionService
        from club_licensing.exceptions import ClubSanctionError
        try:
            ClubSanctionService.assert_allowed(
                club=destination_club, operation="TRANSFER", season=window.season
            )
        except ClubSanctionError as exc:
            raise TransferDeniedError(str(exc)) from exc

        from player_loans.constants import PlayerLoanStatus
        from player_loans.models import PlayerLoan
        if PlayerLoan.objects.select_for_update().filter(
            player=player,
            status__in=[PlayerLoanStatus.PENDING_APPROVAL, PlayerLoanStatus.APPROVED, PlayerLoanStatus.ACTIVE],
        ).exists():
            raise TransferDeniedError("Player has an open loan workflow and cannot enter a permanent transfer.")

        if source_club is None and transfer_fee != 0:
            from .exceptions import InvalidTransferFeeError

            raise InvalidTransferFeeError(
                "A free-agent transfer cannot have a transfer fee."
            )

        existing = (
            Transfer.objects
            .select_for_update()
            .filter(player=player, status__in=Transfer.ACTIVE_STATUSES)
            .first()
        )
        if existing is not None:
            raise TransferAlreadyExistsError(
                "Player already has an active transfer."
            )

        context = TransferEligibilityContext(
            player=player,
            source_club=source_club,
            destination_club=destination_club,
            window=window,
            is_international=is_international,
            now=now,
        )
        effective_is_international = context.effective_is_international

        eligibility = TransferEligibilityService.evaluate(
            player=player,
            source_club=source_club,
            destination_club=destination_club,
            window=window,
            is_international=effective_is_international,
            now=now,
        )
        if eligibility.status == EligibilityStatus.DENIED:
            reason = eligibility.primary_reason
            raise TransferDeniedError(
                reason.message if reason is not None else "Transfer is not eligible."
            )

        status = (
            TransferStatus.REQUIRES_APPROVAL
            if eligibility.status == EligibilityStatus.REQUIRES_APPROVAL
            else TransferStatus.PENDING
        )

        try:
            with transaction.atomic():
                transfer = Transfer.objects.create(
                    player=player,
                    source_club=source_club,
                    destination_club=destination_club,
                    window=window,
                    status=status,
                    transfer_fee=transfer_fee,
                    contract_terms=contract_terms,
                    is_international=effective_is_international,
                )
                _emit_transfer_event(transfer, "transfer.created", {"player_id": str(player.pk)})
                return transfer
        except IntegrityError as exc:
            raise TransferAlreadyExistsError(
                "Transfer could not be created because another active transfer "
                "or database invariant already exists."
            ) from exc


class TransferApprovalService:
    @staticmethod
    @transaction.atomic
    def approve_party(*, transfer_id, role, actor, now=None):
        from .models import TransferApprovalRole, TransferPartyApproval
        from clubs.access import can_manage_club

        transfer, player, source, destination, window = _lock_transfer_transition(transfer_id)
        if transfer.status not in (TransferStatus.PENDING, TransferStatus.REQUIRES_APPROVAL):
            raise TransferNotApprovableError("Only pending transfers can receive party approvals.")
        if role not in TransferApprovalRole.values:
            raise TransferNotApprovableError("Unknown transfer approval role.")
        if role == TransferApprovalRole.PLAYER:
            allowed = player.career.user_id == actor.pk
        elif role == TransferApprovalRole.SOURCE_CLUB:
            if source is None:
                raise TransferNotApprovableError("Free-agent transfers do not require source-club approval.")
            allowed = can_manage_club(actor, source)
        else:
            allowed = can_manage_club(actor, destination)
        if not allowed:
            raise TransferNotApprovableError("Actor cannot approve this transfer party role.")

        approval, created = TransferPartyApproval.objects.get_or_create(
            transfer=transfer, role=role, defaults={"approved_by": actor}
        )
        if created:
            from core_runtime.services import OutboxService
            OutboxService.enqueue(
                topic="transfers",
                event_type="transfer.party_approved",
                aggregate_type="Transfer",
                aggregate_id=transfer.pk,
                deduplication_key=f"transfer:{transfer.pk}:approval:{role}",
                payload={"transfer_id": str(transfer.pk), "role": role, "actor_id": str(actor.pk)},
            )
        required = {TransferApprovalRole.PLAYER, TransferApprovalRole.DESTINATION_CLUB}
        if source is not None:
            required.add(TransferApprovalRole.SOURCE_CLUB)
        approved = set(TransferPartyApproval.objects.filter(transfer=transfer).values_list("role", flat=True))
        if required.issubset(approved):
            return TransferLifecycleService.approve_transfer(transfer_id=transfer.pk, now=now)
        return transfer

    @staticmethod
    def approval_state(*, transfer):
        from .models import TransferApprovalRole
        required = [TransferApprovalRole.PLAYER, TransferApprovalRole.DESTINATION_CLUB]
        if transfer.source_club_id is not None:
            required.insert(1, TransferApprovalRole.SOURCE_CLUB)
        approved = {row.role: row for row in transfer.party_approvals.select_related("approved_by").all()}
        return {
            "required": required,
            "approved": [r for r in required if r in approved],
            "pending": [r for r in required if r not in approved],
        }


class TransferLifecycleService:
    """
    Own the currently implemented Transfer state transitions.

    COMPLETED is a cross-domain atomic workflow.  It revalidates transfer
    eligibility, settles the transfer fee through Economy, ends the source
    contract/roster state, creates and accepts a real destination ContractOffer,
    registers the destination roster membership and only then marks the
    Transfer COMPLETED.  Any failure rolls the entire operation back.
    """

    @staticmethod
    @transaction.atomic
    def approve_transfer(*, transfer_id, now=None):
        transfer, player, source, destination, window = _lock_transfer_transition(
            transfer_id
        )

        if transfer.status not in (
            TransferStatus.PENDING,
            TransferStatus.REQUIRES_APPROVAL,
        ):
            raise TransferNotApprovableError(
                "Only PENDING or REQUIRES_APPROVAL transfers can be approved."
            )

        effective_now = now or timezone.now()
        if timezone.is_naive(effective_now):
            effective_now = timezone.make_aware(effective_now)

        eligibility = TransferEligibilityService.evaluate(
            player=player,
            source_club=source,
            destination_club=destination,
            window=window,
            is_international=transfer.is_international,
            now=effective_now,
            current_transfer_id=transfer.pk,
        )
        if eligibility.is_denied:
            reason = eligibility.primary_reason
            raise TransferDeniedError(
                reason.message if reason is not None else "Transfer is no longer eligible."
            )

        transfer.status = TransferStatus.APPROVED
        transfer.approved_at = effective_now
        transfer.rejected_at = None
        transfer.cancelled_at = None
        transfer.rejection_reason = ""
        transfer.save(
            update_fields=[
                "status",
                "approved_at",
                "rejected_at",
                "cancelled_at",
                "rejection_reason",
                "updated_at",
            ]
        )
        _emit_transfer_event(transfer, "transfer.approved")
        return transfer

    @staticmethod
    @transaction.atomic
    def reject_transfer(*, transfer_id, reason=""):
        transfer = _lock_transfer(transfer_id)
        if transfer.status not in (
            TransferStatus.PENDING,
            TransferStatus.REQUIRES_APPROVAL,
        ):
            raise TransferNotRejectableError(
                "Only pending transfers can be rejected."
            )

        reason = validate_rejection_reason(reason)
        transfer.status = TransferStatus.REJECTED
        transfer.rejected_at = timezone.now()
        transfer.approved_at = None
        transfer.completed_at = None
        transfer.cancelled_at = None
        transfer.rejection_reason = reason
        transfer.save(
            update_fields=[
                "status",
                "rejected_at",
                "approved_at",
                "completed_at",
                "cancelled_at",
                "rejection_reason",
                "updated_at",
            ]
        )
        _emit_transfer_event(transfer, "transfer.rejected", {"reason": reason})
        return transfer

    @staticmethod
    @transaction.atomic
    def cancel_transfer(*, transfer_id):
        transfer = _lock_transfer(transfer_id)
        if transfer.status not in Transfer.ACTIVE_STATUSES:
            raise TransferNotCancellableError(
                "Only an active transfer can be cancelled."
            )

        transfer.status = TransferStatus.CANCELLED
        transfer.cancelled_at = timezone.now()
        transfer.completed_at = None
        transfer.rejected_at = None
        transfer.rejection_reason = ""
        transfer.save(
            update_fields=[
                "status",
                "cancelled_at",
                "completed_at",
                "rejected_at",
                "rejection_reason",
                "updated_at",
            ]
        )
        _emit_transfer_event(transfer, "transfer.cancelled")
        return transfer

    @staticmethod
    @transaction.atomic
    def complete_transfer(
        *,
        transfer_id,
        salary,
        duration_months,
        signing_bonus=0,
        release_clause=None,
        compensation_frequency=None,
        compensation_custom_interval_days=None,
        first_payment_delay_days=0,
        clauses=None,
        destination_squad_scope=ContractRosterScope.SENIOR,
        now=None,
    ):
        """Complete an APPROVED transfer as one atomic business operation.

        Destination employment terms are explicit inputs because the Transfer
        entity deliberately does not duplicate Contract terms.  A real
        ContractOffer is created and accepted so Contract remains the source of
        truth for salary, signing bonus, duration and release clause.
        """
        transfer, player, source, destination, window = _lock_transfer_transition(
            transfer_id
        )

        # Idempotent completion retry.  A COMPLETED row is only valid when the
        # database audit links required by the completion constraints exist.
        if transfer.status == TransferStatus.COMPLETED:
            return transfer

        if transfer.status != TransferStatus.APPROVED:
            raise TransferInvalidStatusError(
                "Only an APPROVED transfer can be completed."
            )

        effective_now = now or timezone.now()
        if timezone.is_naive(effective_now):
            effective_now = timezone.make_aware(effective_now)

        eligibility = TransferEligibilityService.evaluate(
            player=player,
            source_club=source,
            destination_club=destination,
            window=window,
            is_international=transfer.is_international,
            now=effective_now,
            current_transfer_id=transfer.pk,
        )
        if eligibility.is_denied:
            reason = eligibility.primary_reason
            raise TransferDeniedError(
                reason.message if reason is not None
                else "Transfer is no longer eligible for completion."
            )

        from contracts.constants import ContractStatus, ContractTerminationReason
        from contracts.exceptions import (
            ContractError,
            ContractInsufficientFundsError,
            ContractWalletUnavailableError,
        )
        from contracts.models import Contract
        from contracts.services import (
            ContractAcceptanceService,
            ContractLifecycleService,
            ContractOfferService,
        )
        from contracts.validators import (
            validate_money,
            validate_positive_duration,
        )
        from economy.services import WalletService
        from .financial import TransferFinancialService

        if transfer.contract_terms is not None:
            from .negotiation import normalize_terms
            supplied = normalize_terms(dict(salary=salary, duration_months=duration_months,
                signing_bonus=signing_bonus, release_clause=release_clause,
                compensation_frequency=compensation_frequency,
                compensation_custom_interval_days=compensation_custom_interval_days,
                first_payment_delay_days=first_payment_delay_days, clauses=clauses or [],
                destination_squad_scope=destination_squad_scope))
            if supplied != transfer.contract_terms:
                raise TransferCompletionContractError("Completion must use the exact employment terms approved by the player.")

        try:
            salary = validate_money(salary, "salary")
            signing_bonus = validate_money(signing_bonus, "signing_bonus")
            if release_clause is not None:
                release_clause = validate_money(release_clause, "release_clause")
            duration_months = validate_positive_duration(duration_months)
        except ContractError as exc:
            raise TransferCompletionContractError(
                "Destination contract terms are invalid."
            ) from exc

        source_contract = (
            Contract.objects
            .select_for_update()
            .filter(player=player, status=ContractStatus.ACTIVE)
            .first()
        )

        if source is None:
            if source_contract is not None:
                raise TransferCompletionContractError(
                    "Free-agent transfer found an unexpected active source contract."
                )
        else:
            if source_contract is None or source_contract.club_id != source.pk:
                raise TransferCompletionContractError(
                    "Source contract no longer matches the approved transfer."
                )

        from player_loans.models import SeasonalClubParticipation
        from player_loans.services import SeasonParticipationService
        participation_date = max(
            window.season.start_date,
            min(timezone.localdate(effective_now), window.season.end_date),
        )
        source_already_counted = bool(
            source is not None
            and SeasonalClubParticipation.objects.filter(
                player=player, season=window.season, club=source
            ).exists()
        )
        source_counts_this_season = bool(
            source is not None
            and (source_already_counted or participation_date > window.season.start_date)
        )
        SeasonParticipationService.assert_can_join(
            player=player,
            season=window.season,
            destination_club=destination,
            source_club=source if source_counts_this_season else None,
        )

        # Resolve the complete financial participant set first, then lock every
        # wallet once in global UUID order.  Both the transfer-fee leg and the
        # later signing-bonus leg retain locks until this outer transaction
        # commits, so pairwise locking alone would not be enough to guarantee a
        # global order across the multi-leg settlement.
        try:
            settlement_wallets = TransferFinancialService.resolve_settlement_wallets(
                transfer=transfer,
                signing_bonus=signing_bonus,
            )
            WalletService.lock_wallets_canonically(wallets=settlement_wallets)
            TransferFinancialService.settle_transfer_fee(transfer=transfer)
        except TransferInsufficientFundsError:
            raise
        except TransferWalletUnavailableError:
            raise
        except TransferCompletionFinancialError:
            raise

        try:
            if source_contract is not None:
                ContractLifecycleService.terminate_contract(
                    contract_id=source_contract.pk,
                    reason=ContractTerminationReason.TRANSFER,
                    terminated_at=effective_now,
                )

            # The offer is an auditable record of the destination employment
            # terms negotiated as part of this transfer.  It is accepted in the
            # same transaction immediately after creation.
            offer = ContractOfferService.create_offer(
                player=player,
                club=destination,
                salary=salary,
                duration_months=duration_months,
                expires_at=timezone.now() + timedelta(minutes=5),
                signing_bonus=signing_bonus,
                transfer_fee=transfer.transfer_fee,
                release_clause=release_clause,
                compensation_frequency=compensation_frequency,
                compensation_custom_interval_days=compensation_custom_interval_days,
                first_payment_delay_days=first_payment_delay_days,
                clauses=clauses or [],
                squad_scope=destination_squad_scope,
            )
            destination_contract = ContractAcceptanceService.accept_offer(
                offer_id=offer.pk,
                start_date=timezone.localdate(effective_now),
            )
        except ContractInsufficientFundsError as exc:
            raise TransferInsufficientFundsError(
                "Destination club cannot fund the contract signing bonus."
            ) from exc
        except ContractWalletUnavailableError as exc:
            raise TransferWalletUnavailableError(
                "Destination contract requires active financial wallets."
            ) from exc
        except ContractError as exc:
            raise TransferCompletionContractError(
                "Transfer could not establish the destination contract safely."
            ) from exc

        from player_loans.constants import ParticipationType
        if source_counts_this_season:
            SeasonParticipationService.register(
                player=player,
                season=window.season,
                club=source,
                participation_type=ParticipationType.PERMANENT,
                registered_on=participation_date,
                source_reference_type="TRANSFER",
                source_reference_id=str(transfer.pk),
            )
            SeasonParticipationService.mark_left(
                player=player,
                season=window.season,
                club=source,
                left_on=participation_date,
            )
        SeasonParticipationService.register(
            player=player,
            season=window.season,
            club=destination,
            participation_type=ParticipationType.PERMANENT,
            registered_on=participation_date,
            source_reference_type="TRANSFER",
            source_reference_id=str(transfer.pk),
        )

        transfer.source_contract = source_contract
        transfer.destination_contract = destination_contract
        transfer.status = TransferStatus.COMPLETED
        transfer.completed_at = effective_now
        transfer.rejected_at = None
        transfer.cancelled_at = None
        transfer.rejection_reason = ""
        transfer.save(
            update_fields=[
                "source_contract",
                "destination_contract",
                "status",
                "completed_at",
                "rejected_at",
                "cancelled_at",
                "rejection_reason",
                "updated_at",
            ]
        )
        _emit_transfer_event(transfer, "transfer.completed", {
            "player_id": str(player.pk), "destination_contract_id": str(destination_contract.pk),
            "transfer_fee": str(transfer.transfer_fee),
        })
        return transfer



def _lock_transfer_context(*, player, source_club, destination_club, window):
    """
    Canonical lock order for transfer creation/approval:

        Clubs (ordered) -> Player -> Season -> Window -> active Contract

    This aligns the Club -> Player prefix with Contract services and ensures
    eligibility evaluates authoritative database rows rather than stale caller
    instances.
    """
    from clubs.models import Club
    from contracts.constants import ContractStatus
    from contracts.models import Contract
    from players.models import Player

    if player is None or not getattr(player, "pk", None):
        raise TransferDeniedError("Player must be a persisted database object.")
    if destination_club is None or not getattr(destination_club, "pk", None):
        raise TransferDeniedError(
            "Destination club must be a persisted database object."
        )
    if window is None or not getattr(window, "pk", None):
        raise TransferDeniedError(
            "Transfer window must be a persisted database object."
        )

    club_ids = {destination_club.pk}
    if source_club is not None:
        if not getattr(source_club, "pk", None):
            raise TransferDeniedError(
                "Source club must be a persisted database object."
            )
        club_ids.add(source_club.pk)

    locked_clubs = list(
        Club.objects.select_for_update().filter(pk__in=club_ids).order_by("pk")
    )
    if len(locked_clubs) != len(club_ids):
        raise TransferDeniedError("One or more transfer clubs do not exist.")
    club_map = {club.pk: club for club in locked_clubs}
    locked_source = club_map.get(source_club.pk) if source_club is not None else None
    locked_destination = club_map[destination_club.pk]

    try:
        locked_player = Player.objects.select_for_update().get(pk=player.pk)
    except Player.DoesNotExist as exc:
        raise TransferDeniedError("Player does not exist.") from exc

    try:
        window_identity = TransferWindow.objects.values("season_id").get(pk=window.pk)
    except TransferWindow.DoesNotExist as exc:
        raise TransferDeniedError("Transfer window does not exist.") from exc

    try:
        Season.objects.select_for_update().get(pk=window_identity["season_id"])
    except Season.DoesNotExist as exc:
        raise TransferDeniedError("Transfer season does not exist.") from exc

    try:
        locked_window = (
            TransferWindow.objects
            .select_for_update()
            .select_related("season")
            .get(pk=window.pk)
        )
    except TransferWindow.DoesNotExist as exc:
        raise TransferDeniedError("Transfer window does not exist.") from exc

    # ContractAcceptanceService locks Player before creating an active contract,
    # so the Player lock above serializes a concurrent contract transition.
    (
        Contract.objects
        .select_for_update()
        .filter(player=locked_player, status=ContractStatus.ACTIVE)
        .first()
    )

    return locked_player, locked_source, locked_destination, locked_window


def _lock_transfer_transition(transfer_id):
    try:
        identity = Transfer.objects.values(
            "player_id",
            "source_club_id",
            "destination_club_id",
            "window_id",
        ).get(pk=transfer_id)
    except Transfer.DoesNotExist as exc:
        raise TransferNotFoundError("Transfer does not exist.") from exc

    # Resolve lightweight instances only to feed the shared canonical locker.
    from clubs.models import Club
    from players.models import Player

    player = Player(pk=identity["player_id"])
    source = (
        Club(pk=identity["source_club_id"])
        if identity["source_club_id"] is not None
        else None
    )
    destination = Club(pk=identity["destination_club_id"])
    window = TransferWindow(pk=identity["window_id"])

    player, source, destination, window = _lock_transfer_context(
        player=player,
        source_club=source,
        destination_club=destination,
        window=window,
    )
    transfer = _lock_transfer(transfer_id)

    if (
        transfer.player_id != player.pk
        or transfer.source_club_id != (source.pk if source is not None else None)
        or transfer.destination_club_id != destination.pk
        or transfer.window_id != window.pk
    ):
        raise TransferAlreadyProcessedError(
            "Transfer participants changed during transition."
        )

    return transfer, player, source, destination, window


def _lock_transfer(transfer_id):
    try:
        return Transfer.objects.select_for_update().get(pk=transfer_id)
    except Transfer.DoesNotExist as exc:
        raise TransferNotFoundError("Transfer does not exist.") from exc
