# Copyright (C) 2018-2024 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 django.urls import reverse
from note.models import NoteUser

from ..forms.surveys.wei2024 import WEIBusInformation2024, WEISurvey2024, WORDS, WEISurveyInformation2024
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 2024",
            email="wei2024@example.com",
            parent_club_id=2,
            membership_fee_paid=12500,
            membership_fee_unpaid=5500,
            membership_start='2024-01-01',
            membership_end='2024-12-31',
            date_start=date.today() + timedelta(days=2),
            date_end='2024-12-31',
            year=2024,
        )

        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 = WEIBusInformation2024(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 = WEISurveyInformation2024(registration)
            for question in WORDS:
                options = list(WORDS[question][1].keys())
                setattr(information, question, random.choice(options))
            information.step = 20
            information.save(registration)
            registration.save()

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

        # Ensure that everyone has its first choice
        for r in WEIRegistration.objects.filter(wei=self.wei).all():
            survey = WEISurvey2024(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 = WEISurveyInformation2024(registration)
            for question in WORDS:
                options = list(WORDS[question][1].keys())
                setattr(information, question, random.choice(options))
            information.step = 20
            information.save(registration)
            registration.save()

        # Run algorithm
        WEISurvey2024.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 = WEISurvey2024(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 %

    def test_register_1a(self):
        """
        Test register a first year member to the WEI and complete the survey
        """
        response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)))
        self.assertEqual(response.status_code, 200)

        user = User.objects.create(username="toto", email="toto@example.com")
        NoteUser.objects.create(user=user)
        response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict(
            user=user.id,
            soge_credit=True,
            birth_date=date(2000, 1, 1),
            gender='nonbinary',
            clothing_cut='female',
            clothing_size='XS',
            health_issues='I am a bot',
            emergency_contact_name='NoteKfet2020',
            emergency_contact_phone='+33123456789',
        ))
        qs = WEIRegistration.objects.filter(user_id=user.id)
        self.assertTrue(qs.exists())
        registration = qs.get()
        self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200)
        for question in WORDS:
            # Fill 1A Survey, 20 pages
            # be careful if questionnary form change (number of page, type of answer...)
            response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), {
                question: "1"
            })
            registration.refresh_from_db()
            survey = WEISurvey2024(registration)
            self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302,
                                 302 if survey.is_complete() else 200)
            self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed")
        survey = WEISurvey2024(registration)
        self.assertTrue(survey.is_complete())
        survey.select_bus(self.buses[0])
        survey.save()
        self.assertIsNotNone(survey.information.get_selected_bus())