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 anatomy.views import GenericExamViews as AnatomyExamViews

from anatomy.models import (
    Answer,
    Exam,
    AnatomyQuestion as Question,
    QuestionType,
    Structure,
)

from generic.models import CidUser, CidUserGroup, UserUserGroup

from rad.tests.test_helpers import AssertNotFound, create_cid_user_and_groups

from django.contrib.auth.models import User

APP_NAME = "anatomy"


def create_exam(db):
    exam: Exam = AnatomyExamViews.Exam.objects.create(
        name="Cid Exam", exam_mode=True, active=True, open_access=False, exam_open_access=False
    )

    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.anatomy_cid_user_groups.all()

    assert exam

    return exam


def create_structures(db):
    structure = Structure.objects.create(name="test structure")
    structure.save()
    structure = Structure.objects.create(name="test structure 2")
    structure.save()


def create_question_types(db):
    question_type = QuestionType.objects.create(text="test question type")
    question_type.save()
    question_type = QuestionType.objects.create(text="test question type 2")
    question_type.save()


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

    image = tempfile.NamedTemporaryFile(
        dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False
    )

    question: Question = Question.objects.create(
        question_type=QuestionType.objects.first(),
        structure=Structure.objects.first(),
        image=image.name,
    )
    question.exams.set([exam])

    # Create an answer for the question
    if answer is None:
        answer_text = "testanswer"
    else:
        answer_text = answer
    answer = Answer(
        question=question,
        answer=answer_text,
        answer_compare=answer_text,
        status=Answer.MarkOptions.CORRECT,
    )
    answer.save()

    return question


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

    create_structures(db)
    create_question_types(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 = 1
    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

        # Check that we can access the exam json
        res = client.get(exam_metadata["url"])
        assert res.status_code == 200
        exam_json = json.loads(res.content)

        assert (
            question1.get_question_json(answers=False)
            in exam_json["questions"].values()
        )

        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"anatomy/{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"anatomy/{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"anatomy/{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")

        valid_answers = []

        for qid in exam_json["questions"]:
                post_data = {
                    "eid": exam_json["eid"],
                    "cid": post_cid,
                    "qid": qid,
                    "qidn": "1",
                    "ans": f"answer {qid}",
                }
                if testing_cid_user:
                    post_data["passcode"] = cid_user.passcode

                valid_answers.append(post_data)

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

        # 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"] == True
        assert json_res["question_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": "no-results"})
        assert "Results are not currently published." in str(alert)

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

        assert len(answer_lis) == len(valid_answers)

        for n in range(
            len(valid_answers)
        ):  # Check no answers are visible if results not published
            answer = valid_answers[n]
            li = answer_lis[n]

            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")
        assert len(answer_lis) == len(valid_answers) + 1
        assert "Not answered" in str(answer_lis[-1])

        # 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")

        assert len(answer_lis) == len(valid_answers) + 1
        assert "Not answered" in str(answer_lis[-1])

        # Repetition....
        for n in range(
            len(valid_answers)
        ):  # Check answers are visible if results published
            answer = valid_answers[n]
            li = answer_lis[n]

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

            assert li.find("span", {"class": "correct-answer"}).string == "testanswer"

        if users_tested == 1:
            # First user the answers will not have been saved
            assert (
                cid_exam_scores_soup.find("div", {"class": "score-overview"})
                .find("span", {"id": "total-score"})
                .string
                == "0 (3 unmarked)"
            )
        else:
            # Subsequent users should already have answers marked (apart from the last question which we have deleted)
            assert (
                cid_exam_scores_soup.find("div", {"class": "score-overview"})
                .find("span", {"id": "total-score"})
                .string
                == "4 (1 unmarked)"
            )


        for question in exam.exam_questions.all():
            try:
                new_ans = Answer(
                    question=question,
                    answer=question.get_unmarked_user_answers().pop(),
                    status=Answer.MarkOptions.CORRECT,
                )
                new_ans.save()
            except KeyError:  # Final question does not have an answer
                pass

        # 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
            == "6"
        )

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