import pytest
from django.db.models.signals import post_save
from factory.django import mute_signals

from grandchallenge.evaluation.models import Result, Config
from grandchallenge.evaluation.tasks import calculate_ranks
from tests.factories import ResultFactory, ChallengeFactory, UserFactory


@pytest.mark.django_db
@pytest.mark.parametrize(
    "score_method", (Config.ABSOLUTE, Config.MEDIAN, Config.MEAN)
)
def test_calculate_ranks(score_method):
    challenge = ChallengeFactory()
    challenge.evaluation_config.score_jsonpath = "a"
    challenge.evaluation_config.scoring_method_choice = score_method
    challenge.evaluation_config.extra_results_columns = [
        {"path": "b", "title": "b", "order": "desc"}
    ]
    challenge.evaluation_config.save()

    with mute_signals(post_save):
        queryset = (
            ResultFactory(challenge=challenge, metrics={"a": 0.1}),
            ResultFactory(challenge=challenge, metrics={"a": 0.5}),
            ResultFactory(challenge=challenge, metrics={"a": 1.0}),
            ResultFactory(challenge=challenge, metrics={"a": 0.7}),
            ResultFactory(challenge=challenge, metrics={"a": 0.5}),
            ResultFactory(challenge=challenge, metrics={"a": 1.0}),
            ResultFactory(
                challenge=challenge, metrics={"b": 0.3}  # Invalid result
            ),
        )

    # An alternative implementation could be [4, 3, 1, 2, 3, 1] as there are
    # only 4 unique values, the current implementation is harsh on poor results
    expected_ranks = [6, 4, 1, 3, 4, 1, 0]
    assert_ranks(challenge, expected_ranks, queryset)

    # now test reverse order
    challenge.evaluation_config.score_default_sort = (
        challenge.evaluation_config.ASCENDING
    )
    challenge.evaluation_config.save()

    expected_ranks = [1, 2, 5, 4, 2, 5, 0]
    assert_ranks(challenge, expected_ranks, queryset)


@pytest.mark.django_db
def test_results_display():
    challenge = ChallengeFactory()
    challenge.evaluation_config.score_jsonpath = "a"
    challenge.evaluation_config.result_display_choice = Config.ALL
    challenge.evaluation_config.save()

    with mute_signals(post_save):
        user1 = UserFactory()
        user2 = UserFactory()
        queryset = (
            ResultFactory(
                challenge=challenge,
                metrics={"b": 0.3},  # Invalid result
                job__submission__creator=user1,
            ),
            ResultFactory(
                challenge=challenge,
                metrics={"a": 0.6},
                job__submission__creator=user1,
            ),
            ResultFactory(
                challenge=challenge,
                metrics={"a": 0.4},
                job__submission__creator=user1,
            ),
            ResultFactory(
                challenge=challenge,
                metrics={"a": 0.2},
                job__submission__creator=user1,
            ),
            ResultFactory(
                challenge=challenge,
                metrics={"a": 0.1},
                job__submission__creator=user2,
            ),
            ResultFactory(
                challenge=challenge,
                metrics={"a": 0.5},
                job__submission__creator=user2,
            ),
            ResultFactory(
                challenge=challenge,
                metrics={"a": 0.3},
                job__submission__creator=user2,
            ),
        )

    expected_ranks = [0, 1, 3, 5, 6, 2, 4]
    assert_ranks(challenge, expected_ranks, queryset)

    challenge.evaluation_config.result_display_choice = Config.MOST_RECENT
    challenge.evaluation_config.save()

    expected_ranks = [0, 0, 0, 2, 0, 0, 1]
    assert_ranks(challenge, expected_ranks, queryset)

    challenge.evaluation_config.result_display_choice = Config.BEST
    challenge.evaluation_config.save()

    expected_ranks = [0, 1, 0, 0, 0, 2, 0]
    assert_ranks(challenge, expected_ranks, queryset)

    # now test reverse order
    challenge.evaluation_config.score_default_sort = (
        challenge.evaluation_config.ASCENDING
    )
    challenge.evaluation_config.save()

    expected_ranks = [0, 0, 0, 2, 1, 0, 0]
    assert_ranks(challenge, expected_ranks, queryset)

    challenge.evaluation_config.result_display_choice = Config.MOST_RECENT
    challenge.evaluation_config.save()

    expected_ranks = [0, 0, 0, 1, 0, 0, 2]
    assert_ranks(challenge, expected_ranks, queryset)


def assert_ranks(challenge, expected_ranks, queryset):
    # Execute calculate_ranks manually
    calculate_ranks(challenge_pk=challenge.pk)

    for q, exp in zip(queryset, expected_ranks):
        r = Result.objects.get(pk=q.pk)
        assert r.rank == exp

@@ -407,16 +407,89 @@
 MEAN)%0A)%0A
+@pytest.mark.parametrize(%22order%22, (Config.DESCENDING, Config.ASCENDING))%0A
 def test
@@ -517,16 +517,23 @@
 e_method
+, order
 ):%0A    c
@@ -684,16 +684,75 @@
 _method%0A
+    challenge.evaluation_config.score_default_sort = order%0A
     chal
@@ -1486,24 +1486,63 @@
 %0A        )%0A%0A
+    if order == Config.DESCENDING:%0A    
     # An alt
@@ -1591,16 +1591,19 @@
  2, 3, 1
+, 0
 %5D as the
@@ -1608,18 +1608,22 @@
 here
- are%0A
+%0A    
     #
+ are
  onl
@@ -1684,16 +1684,26 @@
  on poor
+%0A        #
  results
@@ -1703,16 +1703,20 @@
 results%0A
+    
     expe
@@ -1758,173 +1758,23 @@
     
-assert_ranks(challenge, expected_ranks, queryset)%0A%0A    # now test reverse order%0A    challenge.evaluation_config.score_default_sort = (%0A        challenge.evaluation_c
+elif order == C
 onfi
@@ -1788,55 +1788,14 @@
 DING
+:
 %0A    
-)%0A    challenge.evaluation_config.save()%0A%0A
     
@@ -1828,24 +1828,69 @@
  4, 2, 5, 0%5D
+%0A    else:%0A        raise NotImplementedError%0A
 %0A    assert_

Refactor ranking test