# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

import random
from datetime import date, timedelta

from django.contrib.auth.models import User
from django.test import TestCase

from ..forms.surveys.wei2023 import WEIBusInformation2023, WEISurvey2023, WORDS, WEISurveyInformation2023
from ..models import Bus, WEIClub, WEIRegistration


class TestWEIAlgorithm(TestCase):
    """
    Run some tests to ensure that the WEI algorithm is working well.
    """
    fixtures = ('initial',)

    def setUp(self):
        """
        Create some test data, with one WEI and 10 buses with random score attributions.
        """
        self.user = User.objects.create_superuser(
            username="weiadmin",
            password="admin",
            email="admin@example.com",
        )
        self.user.save()
        self.client.force_login(self.user)
        sess = self.client.session
        sess["permission_mask"] = 42
        sess.save()

        self.wei = WEIClub.objects.create(
            name="WEI 2023",
            email="wei2023@example.com",
            parent_club_id=2,
            membership_fee_paid=12500,
            membership_fee_unpaid=5500,
            membership_start='2023-01-01',
            membership_end='2023-12-31',
            date_start=date.today() + timedelta(days=2),
            date_end='2023-12-31',
            year=2023,
        )

        self.buses = []
        for i in range(10):
            bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10)
            self.buses.append(bus)
            information = WEIBusInformation2023(bus)
            for question in WORDS:
                information.scores[question] = {answer: random.randint(1, 5) for answer in WORDS[question][1]}
            information.save()
            bus.save()

    def test_survey_algorithm_small(self):
        """
        There are only a few people in each bus, ensure that each person has its best bus
        """
        # Add a few users
        for i in range(10):
            user = User.objects.create(username=f"user{i}")
            registration = WEIRegistration.objects.create(
                user=user,
                wei=self.wei,
                first_year=True,
                birth_date='2000-01-01',
            )
            information = WEISurveyInformation2023(registration)
            for question in WORDS:
                setattr(information, question, random.randint(1, 5))
            information.step = 20
            information.save(registration)
            registration.save()

        # Run algorithm
        WEISurvey2023.get_algorithm_class()().run_algorithm()

        # Ensure that everyone has its first choice
        for r in WEIRegistration.objects.filter(wei=self.wei).all():
            survey = WEISurvey2023(r)
            preferred_bus = survey.ordered_buses()[0][0]
            chosen_bus = survey.information.get_selected_bus()
            self.assertEqual(preferred_bus, chosen_bus)

    def test_survey_algorithm_full(self):
        """
        Buses are full of first year people, ensure that they are happy
        """
        # Add a lot of users
        for i in range(95):
            user = User.objects.create(username=f"user{i}")
            registration = WEIRegistration.objects.create(
                user=user,
                wei=self.wei,
                first_year=True,
                birth_date='2000-01-01',
            )
            information = WEISurveyInformation2023(registration)
            for question in WORDS:
                setattr(information, question, random.randint(1, 5))
            information.step = 20
            information.save(registration)
            registration.save()

        # Run algorithm
        WEISurvey2023.get_algorithm_class()().run_algorithm()

        penalty = 0
        # Ensure that everyone seems to be happy
        # We attribute a penalty for each user that didn't have its first choice
        # The penalty is the square of the distance between the score of the preferred bus
        # and the score of the attributed bus
        # We consider it acceptable if the mean of this distance is lower than 5 %
        for r in WEIRegistration.objects.filter(wei=self.wei).all():
            survey = WEISurvey2023(r)
            chosen_bus = survey.information.get_selected_bus()
            buses = survey.ordered_buses()
            score = min(v for bus, v in buses if bus == chosen_bus)
            max_score = buses[0][1]
            penalty += (max_score - score) ** 2

            self.assertLessEqual(max_score - score, 25)  # Always less than 25 % of tolerance

        self.assertLessEqual(penalty / 100, 25)  # Tolerance of 5 %
