"""Timed, self-attended group practice. Lock order: Club -> Player -> Session."""
from datetime import timedelta
from decimal import Decimal

from django.db import transaction
from django.utils import timezone

from .drills import KEY, VERSION, formation_identity, weights
from .exceptions import TrainingValidationError
from .models import (FormationTrainingProgress, PlayerLegacyAsset, 
                     TrainingParticipant, TrainingSession)
from .scheduling_policy import check_schedule_date
from .validators import validate_training_attribute


def personal_facility(player):
    asset = PlayerLegacyAsset.objects.filter(assigned_player=player, asset_type="TRAINING_FACILITY").first()
    level = asset.level if asset else 1
    multiplier = Decimal("1") + Decimal(level - 1) * Decimal("0.08")
    return multiplier, {"asset_id": str(asset.pk) if asset else None, "level": level, "multiplier": str(multiplier)}


def check_individual_capacity(player, day):
    count = TrainingParticipant.objects.filter(player=player, session__session_type="INDIVIDUAL", session__training_date=day).exclude(session__status="CANCELLED").count()
    if count >= 2:
        raise TrainingValidationError("At most two individual sessions per day.")


@transaction.atomic
def schedule_slot(*, club_id, training_date, target_attribute, starts_at, ends_at,
                  slot_number, formation, formation_slots=None, player_ids=None):
    from .services import _lock_club, _validate_club_active
    club = _lock_club(club_id)
    _validate_club_active(club)
    check_schedule_date(training_date)
    validate_training_attribute(target_attribute)
    key, slots = formation_identity(formation, formation_slots)
    if player_ids is not None:
        raise TrainingValidationError("Players must attend themselves; roster auto-enrolment is disabled.")
    if slot_number not in (1, 2) or timezone.is_naive(starts_at) or timezone.is_naive(ends_at):
        raise TrainingValidationError("Use slot 1 or 2 and timezone-aware times.")
    if not timedelta(minutes=15) <= ends_at - starts_at <= timedelta(hours=2):
        raise TrainingValidationError("Session duration must be 15–120 minutes.")
    from datetime import timezone as utc_timezone
    if starts_at.astimezone(utc_timezone.utc).date() != training_date or starts_at < timezone.now():
        raise TrainingValidationError("Session must start in the future on its UTC training date.")
    all_sessions = TrainingSession.objects.filter(club=club, session_type="TEAM").exclude(status="CANCELLED")
    sessions = all_sessions.filter(training_date=training_date)
    if sessions.count() >= 2 or sessions.filter(slot_number=slot_number).exists() or all_sessions.filter(starts_at__lt=ends_at, ends_at__gt=starts_at).exists():
        raise TrainingValidationError("Two non-overlapping team sessions per day; each slot may be used once.")
    return TrainingSession.objects.create(club=club, training_date=training_date, target_attribute=target_attribute,
        starts_at=starts_at, ends_at=ends_at, slot_number=slot_number, formation=formation,
        formation_key=key, formation_slots=slots, session_type="TEAM", policy_key=KEY, policy_version=VERSION)


@transaction.atomic
def attend(*, session_id, player_id, user):
    from clubs.models import ClubSquadMembership
    from club_assets.models import ClubAsset
    from club_assets.services import TrainingGroundEffectService
    from .services import (_lock_club, _lock_player, _validate_club_active,
                           _validate_player_active, TrainingExecutionService)
    identity = TrainingSession.objects.filter(pk=session_id).values("club_id", "starts_at").first()
    if not identity or not identity["club_id"] or not identity["starts_at"]:
        raise TrainingValidationError("Not a timed team session.")
    club = _lock_club(identity["club_id"])
    player = _lock_player(player_id)
    if player.career.user_id != user.pk:
        raise TrainingValidationError("Only the player owner can attend; coaches cannot credit the roster.")
    _validate_club_active(club)
    _validate_player_active(player)
    session = TrainingSession.objects.select_for_update().get(pk=session_id)
    previous = TrainingParticipant.objects.filter(session=session, player=player, checked_in_at__isnull=False).first()
    if previous:
        return session  # Stable retry after the window closes or player transfers.
    now = timezone.now()
    if session.status != "SCHEDULED" or not session.starts_at <= now < session.ends_at:
        raise TrainingValidationError("Attendance is available only inside the scheduled training window.")
    from .club_players import roster_ids
    if player.pk not in roster_ids(club.pk,lock=True):
        raise TrainingValidationError("Player is not on the club's current roster.")
    count = TrainingParticipant.objects.filter(player=player, session__session_type="TEAM", session__training_date=session.training_date).exclude(session__status="CANCELLED").count()
    if count >= 2:
        raise TrainingValidationError("At most two group sessions per player per day, including transfers.")
    # Hold the assigned ground stable while both skill and formation credits resolve.
    assets = list(ClubAsset.objects.select_for_update().filter(primary_for_club=club, owner_club=club, asset_type="TRAINING_GROUND"))
    multiplier = TrainingGroundEffectService.resolve_multiplier(club=club)
    participant = TrainingParticipant.objects.create(session=session, player=player, checked_in_at=now,
        evidence={"facility_multiplier": str(multiplier), "facility": [{"id": str(a.pk), "level": a.level, "condition": a.condition, "status": a.status} for a in assets],
                  "weights": {k: str(v) for k,v in weights(session.target_attribute).items()}})
    results = TrainingExecutionService.execute(session_id=session.pk, attending_player_id=player.pk)
    # Health/fatigue/nutrition/staff modifiers affect learning too; medical prohibition rolls everything back.
    gain = min(Decimal("2"), next(r.final_effect for r in results if r.attribute_name == session.target_attribute))
    progress, _ = FormationTrainingProgress.objects.select_for_update().get_or_create(club=club, player=player, formation_key=session.formation_key)
    before = Decimal(progress.points)
    progress.points = min(Decimal("100"), before + gain)
    progress.save(update_fields=["points", "updated_at"])
    participant.evidence.update({"formation_key": session.formation_key, "formation_before": str(before), "formation_after": str(progress.points)})
    participant.save(update_fields=["evidence"])
    return session


@transaction.atomic
def close_slot(session_id):
    identity = TrainingSession.objects.filter(pk=session_id).values("club_id").first()
    if not identity:
        return False
    from .services import _lock_club
    _lock_club(identity["club_id"])
    session = TrainingSession.objects.select_for_update().get(pk=session_id)
    if session.starts_at and session.status == "SCHEDULED" and session.ends_at <= timezone.now():
        session.status = "COMPLETED"
        session.completed_at = timezone.now()
        session.save(update_fields=["status", "completed_at"])
        return True
    return False


def close_due_slots(limit=100):
    ids = list(TrainingSession.objects.filter(status="SCHEDULED", ends_at__lte=timezone.now()).order_by("ends_at").values_list("pk", flat=True)[:limit])
    return sum(close_slot(pk) for pk in ids)


def formation_points(club_id, player_ids, key):
    return {str(p): float(v) for p,v in FormationTrainingProgress.objects.filter(club_id=club_id, player_id__in=player_ids, formation_key=key).values_list("player_id", "points")}
