# apps/participants/services.py

from django.db import transaction
from .models import Participant
from apps.settingsapp.models import GradingSettings

def _factor(diff: float) -> float:
    if diff <= 10:
        return 1.0
    elif diff <= 20:
        return 0.8
    return 0.6

def calculate_scores():
    """Recalcula y persiste sólo el campo 'score' (base + similitud + bonus objetivo)."""
    settings = GradingSettings.get()
    participants = list(Participant.objects.all())

    # 1) Base + similitud
    for p in participants:
        base = settings.report_weight + settings.presentation_weight
        sim_total = sum(
            _factor(getattr(p, f"{fld}_diff")) * getattr(settings, f"{fld}_weight")
            for fld in ("R", "L", "Ph", "Pcu")
        )
        p.score = base + sim_total

    # 2) Calculamos bonus de objetivo según ranking de s_vol_over_eta (mayor es mejor)
    sorted_obj = sorted(participants, key=lambda x: x.s_vol_over_eta, reverse=True)
    obj_bonus_map = {}
    for idx, p in enumerate(sorted_obj, start=1):
        bonus = max(settings.objective_weight - (idx - 1), 0)
        obj_bonus_map[p.pk] = bonus

    # 3) Persistimos sumando el bonus a score
    with transaction.atomic():
        for p in participants:
            p.score += obj_bonus_map[p.pk]
            p.save(update_fields=["score"])


def calculate_scores_details():
    """
    Devuelve una lista de dicts con desgloses:
      [{
        participant: <Participant>,
        base: float,
        sim_total: float,
        obj_bonus: float,
        total: float,
        objective_metric: float
      }, ...]
    ordenada exclusivamente por 'objective_metric' (s_vol_over_eta) descendente.
    """
    settings = GradingSettings.get()
    participants = list(Participant.objects.all())

    # 1) Calculamos base y similitud
    details = []
    for p in participants:
        base = settings.report_weight + settings.presentation_weight
        sim_total = sum(
            _factor(getattr(p, f"{fld}_diff")) * getattr(settings, f"{fld}_weight")
            for fld in ("R", "L", "Ph", "Pcu")
        )
        details.append({
            "participant": p,
            "base": base,
            "sim_total": sim_total,
            "obj_bonus": 0,            # se asignará más abajo
            "total": base + sim_total, # se incrementará con obj_bonus
            "objective_metric": p.s_vol_over_eta,
        })

    # 2) Asignamos bonus de objetivo según posición en objective_metric
    sorted_by_obj = sorted(details, key=lambda d: d["objective_metric"], reverse=True)
    for idx, entry in enumerate(sorted_by_obj, start=1):
        bonus = max(settings.objective_weight - (idx - 1), 0)
        entry["obj_bonus"] = bonus
        entry["total"] += bonus

    # 3) Orden final sólo por objective_metric descendente
    return sorted(details, key=lambda d: d["objective_metric"], reverse=True)
