from collections import defaultdict
import json
import os
from re import A
from django.conf import settings
import pytest

import tempfile

# from django.contrib.auth.models import User
from django.urls import reverse

from rich.pretty import pprint

from bs4 import BeautifulSoup

from longs.views import GenericExamViews as LongsExamViews

from longs.models import (
    AnswerMarks,
    UserAnswer,
    Exam,
    Examination,
    Long as Question,
    LongSeries,
    LongSeriesImage,

)

from generic.models import CidUser, CidUserGroup, UserUserGroup

from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied

from rad.tests.test_helpers import AssertNotFound, create_cid_user_and_groups



APP_NAME = "longs"


def create_exam(db):
    exam: Exam = LongsExamViews.Exam.objects.create(
        name="Cid Exam", exam_mode=True, active=True
    )

    group1: CidUserGroup = CidUserGroup.objects.get(name="Group1")
    usergroup1: UserUserGroup = UserUserGroup.objects.get(name="Group1")

    exam.cid_user_groups.add(group1)

    exam.valid_cid_users.add(*group1.ciduser_set.all())
    exam.valid_user_users.add(*usergroup1.users.all())

    assert exam in group1.longs_cid_user_groups.all()

    assert exam

    return exam


def create_examinations(db):
    examination = Examination.objects.create(examination="test examination")
    examination.save()
    examination = Examination.objects.create(examination="test examination 2")
    examination.save()


def create_long_series(question, image_number=5):
    series = LongSeries(
        description="Test series",
        examination=Examination.objects.all().first(),
    )

    series.save()

    for n in range(image_number):
        create_long_series_image(series, n)

    return series


def create_long_series_image(series, n=0):
    image = tempfile.NamedTemporaryFile(
        dir=settings.MEDIA_ROOT, suffix="{n}.jpg", delete=False
    )

    long_series_image = LongSeriesImage(image=image.name, series=series)

    long_series_image.save()


def create_question(exam, answer=None, normal=False):
    # Just assign random stuff

    question: Question = Question.objects.create(
        description="Test question",
        history="Test history",
        feedback="Test feedback",
        model_observations="Model obs",
        model_interpretation="Model interp",
        model_principle_diagnosis="Model pd",
        model_differential_diagnosis="Model dd",
        model_management="Model management",
    )

    series = create_long_series(question=question)

    question.series.add(series)

    question.exams.set([exam])

    return question


def test_exams(db, client):
    create_cid_user_and_groups(db)
    exam = create_exam(db)

    # create_structures(db)
    # create_question_types(db)
    create_examinations(db)

    question1 = create_question(exam)
    generated_questions = [question1, create_question(exam), create_question(exam)]

    active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
    assert active_exams.status_code == 200
    assert len(json.loads(active_exams.content)["exams"]) == 0

    cid_user_1000 = CidUser.objects.get(cid=1000)
    cid_user_1001 = CidUser.objects.get(cid=1001)
    cid_user_2001 = CidUser.objects.get(cid=2001)
    user1 = User.objects.get(username="user1")

    # Test exam access urls

    # Check that we can't access without logging in
    assert client.get(exam.get_json_url()).status_code == 404
    assert (
        client.get(
            exam.get_json_url(cid=cid_user_2001.cid, passcode=cid_user_2001.passcode)
        ).status_code
        == 404
    )
    # Valid user but incorrect passcode
    assert (
        client.get(
            exam.get_json_url(cid=cid_user_1001.cid, passcode=cid_user_2001.passcode)
        ).status_code
        == 404
    )

    users_tested = 0
    for cid_user in [cid_user_1001, cid_user_1000, user1]:
        if isinstance(cid_user, CidUser):
            testing_cid_user = True
        else:
            testing_cid_user = False
            current_user = cid_user
            client.force_login(cid_user)

        exam.active = True
        exam.publish_results = False
        exam.save()

        # Test active exams sections works as intended
        if testing_cid_user:
            active_exams_url = reverse(
                f"active_exams_cid",
                kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode},
            )
        else:
            active_exams_url = reverse(
                f"active_exams",
            )

        active_exams_cid = client.get(active_exams_url)

        assert active_exams_cid.status_code == 200
        assert len(json.loads(active_exams_cid.content)["exams"]) == 1

        if testing_cid_user:
            no_active_exams_cid = client.get(
                reverse(f"active_exams_cid", kwargs={"cid": 2001, "passcode": "EGFH"})
            )

            assert no_active_exams_cid.status_code == 401
            assert json.loads(no_active_exams_cid.content)["status"] == "invalid"

            invalid_passcode = client.get(
                reverse(
                    f"active_exams_cid",
                    kwargs={"cid": cid_user.cid, "passcode": "AAAA"},
                )
            )
            assert invalid_passcode.status_code == 401
            assert json.loads(invalid_passcode.content)["status"] == "invalid"

        exam_metadata = json.loads(active_exams_cid.content)["exams"][0]

        assert exam_metadata["name"] == "Cid Exam"
        assert exam_metadata["exam_active"] == True
        assert exam_metadata["exam_mode"] == True

        res = client.get(exam_metadata["url"])
        assert res.status_code == 200
        exam_json = json.loads(res.content)

        # We can also check the unbased questions
        res = client.get(exam_metadata["url"] + "/unbased")
        assert res.status_code == 200

        exam_json_unbased = json.loads(res.content)

        # This show we have different types (int vs string)
        assert (
            set([str(i) for i in exam_json["exam_order"]])
            == set(exam_json["questions"].keys())
            == set(exam_json["question_requests"].keys())
            == set([str(i) for i in exam_json_unbased["exam_order"]])
            == set(exam_json_unbased["questions"].keys())
        )

        for q in exam_json["question_requests"]:
            # check redirects to the json file work
            # TODO: sort out testing of the saved JSON files
            try:
                question_json_res = client.get(
                    reverse(
                        f"longs:exam_question_json_cid",
                        kwargs={
                            "pk": exam.pk,
                            "sk": q,
                            "cid": cid_user.cid,
                            "passcode": cid_user.passcode,
                        },
                    ),
                    follow=True,
                )
            except AttributeError: # UserUser has no cid attribute
                question_json_res = client.get(
                    reverse(
                        f"longs:exam_question_json",
                        kwargs={
                            "pk": exam.pk,
                            "sk": q,
                        },
                    ),
                    follow=True,
                )
            # assert question_json_res.status_code == 200
            assert len(question_json_res.redirect_chain) == 2
            assert question_json_res.redirect_chain[0][1] == 302
            assert question_json_res.redirect_chain[1][1] == 302

            assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json")

            if testing_cid_user:
                question_json_unbased_res = client.get(
                    reverse(
                        f"longs:exam_question_json_unbased_cid",
                        kwargs={
                            "pk": exam.pk,
                            "sk": q,
                            "cid": cid_user.cid,
                            "passcode": cid_user.passcode,
                        },
                    ),
                    follow=True,
                )
            else:
                test =                     reverse(
                        f"longs:exam_question_json_unbased",
                        kwargs={
                            "pk": exam.pk,
                            "sk": q,
                        },
                    )
                question_json_unbased_res = client.get(

                    reverse(
                        f"longs:exam_question_json_unbased",
                        kwargs={
                            "pk": exam.pk,
                            "sk": q,
                        },
                    ),
                    follow=True,
                )

            assert question_json_unbased_res.status_code == 200
            question_json = json.loads(question_json_unbased_res.content)

            # question_json_res = client.get(question1.get_question_json(answers=False, based=True))

            # As we store references to the questions we have to check individually
            assert q in exam_json["questions"].keys()

            q_json = Question.objects.get(pk=q).get_question_json(based=False)

            assert question_json == q_json

        assert len(exam_json["questions"]) == 3

        exam.active = False
        exam.save()

        res = client.get(exam_metadata["url"])
        assert res.status_code == 404

        exam.active = True
        exam.save()

        if testing_cid_user:
            post_cid = cid_user.cid
        else:
            post_cid = f"u-{current_user.pk}"

        post_json = {
            "eid": f"long/{exam.pk}",
            "cid": post_cid,
            "start_time": "1659618141.731",
            "answers": [[]],
        }

        # Test submission
        # Start with 0 answers
        res = client.post(reverse("global_exam_answers_submit"), data=post_json)

        json_res = json.loads(res.content)

        assert json_res["success"] == True
        assert json_res["question_count"] == 0

        post_json = {
            "eid": f"long/{exam.pk+10}",
            "cid": post_cid,
            "start_time": "1659618141.731",
            "answers": [[]],
        }
        res = client.post(reverse("global_exam_answers_submit"), data=post_json)
        json_res = json.loads(res.content)
        assert json_res["success"] == False

        # Test invalid cid
        post_json = {
            "eid": f"long/{exam.pk+10}",
            "cid": 1,
            "start_time": "1659618141.731",
            "answers": [[]],
        }
        res = client.post(reverse("global_exam_answers_submit"), data=post_json)
        json_res = json.loads(res.content)
        assert json_res["success"] == False
        assert json_res["error"] in ("Invalid data", "user / cid mismatch")

        # Long answers currently send a seperate object for each component
        valid_answers = []
        valid_answers_extra = []
        valid_answers_by_question = defaultdict(dict)

        sections = (
            ("1", "answer observations"),
            ("2", "answer interpretation"),
            ("3", "answer principle diagnosis"),
            ("4", "answer differential diagnosis"),
            ("5", "answer management"),
        )

        i = 0
        for qid in exam_json["questions"]:
            # Long answers submit 5 sections
            for n, name in sections:
                post_data = {
                    "eid": exam_json["eid"],
                    "cid": post_cid,
                    "qid": qid,
                    "qidn": n,
                    "ans": f"{name} - {q}",
                }
                if testing_cid_user:
                    post_data["passcode"] = cid_user.passcode

                valid_answers_extra.append(post_data)
                valid_answers_by_question[i][n] = post_data
            i = i + 1

        post_json = {
            "eid": exam_json["eid"],
            "cid": post_cid,
            "start_time": "1659618141.731",
            "answers": json.dumps([*valid_answers, *valid_answers_extra]),
        }

        # Test submission
        # give three answers
        res = client.post(reverse("global_exam_answers_submit"), data=post_json)

        json_res = json.loads(res.content)

        assert json_res["success"] is True
        assert json_res["question_count"] == 5 * 3

        # Check the answers now exist in the database
        if testing_cid_user:
            answers = UserAnswer.objects.filter(cid=cid_user.cid)
        else:
            answers = UserAnswer.objects.filter(user=cid_user)

        assert answers.count() == 3

        # We can then check the answers have been submitted (and can be viewed by the user)

        if testing_cid_user:
            # Get overview scores page (invalid passcode)
            AssertNotFound(
                client,
                reverse(
                    f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"}
                ),
            )

            cid_scores_url = reverse(
                f"cid_scores",
                kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode},
            )
        else:
            cid_scores_url = reverse(f"user_scores")

        # Get overview scores page
        cid_scores_res = client.get(cid_scores_url)

        cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser")

        search_exam = (
            cid_scores_soup.find("div", {"id": "exam-assigned"})
            .find("ul", {"class": exam.app_name})
            .find("li", attrs={"data-exam-id": exam.pk})
        )
        assert search_exam
        assert str(exam) in str(search_exam)
        assert len(search_exam.find("button", {"class": "start-button"})) > 0
        # assert "Active" in assigned_exams # check it is active

        invalid_exam = cid_scores_soup.find_all(
            "li", attrs={"data-exam-id": exam.pk + 5}
        )
        assert not invalid_exam  # Check we can't find an invalid exam

        # Check that we have a results link
        results_exam = (
            cid_scores_soup.find("div", {"id": "exam-results"})
            .find("ul", {"class": exam.app_name})
            .find_all("li", attrs={"data-exam-id": exam.pk})
        )
        assert results_exam
        assert str(exam) in str(results_exam)
        assert str(exam) + " test" not in str(results_exam)
        assert "Results Published" not in str(
            results_exam
        )  # It should not be published yet

        if testing_cid_user:
            AssertNotFound(
                client,
                reverse(
                    f"{exam.app_name}:exam_scores_cid_user",
                    kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": "AAAA"},
                ),
            )
            AssertNotFound(
                client,
                reverse(
                    f"{exam.app_name}:exam_scores_cid_user",
                    kwargs={
                        "pk": exam.pk,
                        "cid": cid_user_2001.cid,
                        "passcode": cid_user_2001.passcode,
                    },
                ),
            )

            exam_scores_url = reverse(
                f"{exam.app_name}:exam_scores_cid_user",
                kwargs={
                    "pk": exam.pk,
                    "cid": cid_user.cid,
                    "passcode": cid_user.passcode,
                },
            )
        else:
            exam_scores_url = reverse(
                f"{exam.app_name}:exam_scores_user",
                kwargs={"pk": exam.pk},
            )

        # Load the exam results page
        cid_exam_scores_res = client.get(exam_scores_url)
        # Load the exam results page
        cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")

        # Check that we have an alert that exams are not published
        alert = cid_exam_scores_soup.find("div", {"class": "alert"})
        assert "Results are not currently published." in str(alert)

        answer_lis = cid_exam_scores_soup.find(
            "ul", {"class": "score-answer-list"}
        ).find_all("li", {"class": "user-answer-li"})

        assert len(answer_lis) == len(valid_answers_by_question)


        for n in range(
            len(valid_answers_by_question)
        ):  # Check no answers are visible if results not published
            lis = answer_lis[n]
            #n = str(n + 1)  # questions are not zero indexed (lists are)

            for qidn, section_title in sections:
                answer = valid_answers_by_question[n][qidn]

                li = lis.find("li", attrs={"data-qidn": qidn})

                assert li["class"][0].lower() in section_title

                assert answer["ans"] in str(li)  # Check that the saved answer is shown

            # assert not li.find(
            #    "span", {"class": "correct-answer"}
            # )  # and that the correct answer isn't

        assert (
            cid_exam_scores_soup.find("div", {"class": "score-overview"}) is None
        )  # check we hide the score overview if not published


        # Add another question to the exam
        create_question(exam)

        # Load the exam results page (again)
        cid_exam_scores_res = client.get(exam_scores_url)
        cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")

        answer_lis = cid_exam_scores_soup.find(
            "ul", {"class": "score-answer-list"}
        ).find_all("li", {"class": "user-answer-li"})
        assert len(answer_lis) == len(valid_answers_by_question) + 1
        assert "Not answered" in str(answer_lis[-1])
        assert str(answer_lis[-1]).count("Not answered") == 5




        # Publish the results!
        exam.publish_results = True
        exam.save()

        # Load the exam results page (again)
        cid_exam_scores_res = client.get(exam_scores_url)
        cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")

        assert (
            cid_exam_scores_soup.find("div", {"class": "alert"}) is None
        )  # Check our warning banner is gone

        answer_lis = cid_exam_scores_soup.find(
            "ul", {"class": "score-answer-list"}
        ).find_all("li", {"class": "user-answer-li"})

        assert len(answer_lis) == len(valid_answers_by_question) + 1
        assert "Not answered" in str(answer_lis[-1])
        assert str(answer_lis[-1]).count("Not answered") == 5

        # Repetition....
        for n in range(
            len(valid_answers_by_question)
        ):  # Check no answers are visible if results not published
            lis = answer_lis[n]
            #n = str(n + 1)  # questions are not zero indexed (lists are)

            for qidn, section_title in sections:
                answer = valid_answers_by_question[n][qidn]

                li = lis.find("li", attrs={"data-qidn": qidn})

                assert li["class"][0].lower() in section_title

                assert answer["ans"] in str(li)  # Check that the saved answer is shown

        # For long cases an unmarked answers is given a score of 4
        # Each user needs marking individually
        assert (
            cid_exam_scores_soup.find("div", {"class": "score-overview"})
            .find("span", {"id": "total-score"})
            .string
            == "3 (3 unmarked)"
        )

        # Mark the questions
        # For longs this needs to be done for each user
        question: Question
        for question in exam.exam_questions.all():

            answers = question.get_unmarked_user_answers()
            for answer in answers:
                answer.score = UserAnswer.ScoreOptions.EIGHT
                answer.save()

            #try:
            #    new_ans = Answer(
            #        question=question,
            #        answer=question.get_unmarked_user_answers().pop(),
            #        status=Answer.MarkOptions.CORRECT,
            #    )
            #    new_ans.save()
            #except IndexError:  # Final question does not have an answer
            #    pass

        # TODO: test double marking

        # Load the exam results page (again)
        cid_exam_scores_res = client.get(exam_scores_url)
        cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")

        assert (
            cid_exam_scores_soup.find("div", {"class": "score-overview"})
            .find("span", {"id": "total-score"})
            .string
            == "27.0"
        )

        generated_questions.pop(-1).delete()
        users_tested = users_tested + 1

    assert users_tested == 3


    # Some quick tests to check for user access to the marking page
    client.login(username="marker", password="test1234")

    
    # Load the mark overview page
    mark_overview_res = client.get(reverse(
            f"{exam.app_name}:mark_overview",
            kwargs={"pk": exam.pk},
        ))

    assert mark_overview_res.status_code == 403

    exam.markers.add(User.objects.get(username="marker"))


    mark_overview_res = client.get(reverse(
            f"{exam.app_name}:mark_overview",
            kwargs={"pk": exam.pk},
        ))
    assert mark_overview_res.status_code == 200
    mark_overview_soup = BeautifulSoup(mark_overview_res.content, "html.parser")

    to_mark =  mark_overview_soup.find("ul", {"id": "question-mark-list"}).find_all("li")
    assert len(to_mark) == 3

    for n in range(0,3):
        mark_question_res = client.get(reverse(
                f"{exam.app_name}:mark",
                kwargs={"exam_id": exam.pk, "sk": n},
            ))
        assert mark_question_res.status_code == 200
        mark_question_soup = BeautifulSoup(mark_question_res.content, "html.parser")
        print(mark_question_soup)

        #mark_links = mark_question_soup.find_all("a", {"class": "mark-answer-link"})
        #assert len(mark_links) == 2

        #for link in mark_links:
        #    mark_res = client.get(link["href"])
        #    assert mark_res.status_code == 200


    mark_question_res = client.get(reverse(
            f"{exam.app_name}:mark",
            kwargs={"exam_id": exam.pk, "sk": 4},
        ))
    assert mark_question_res.status_code == 404
    mark_question_soup = BeautifulSoup(mark_question_res.content, "html.parser")