import json
import pytest
from django.urls import reverse
from django.contrib.auth.models import User
from django.test import Client
from django.utils import timezone
from anatomy.models import Exam as AnatomyExam, AnatomyQuestion, QuestionType, Structure
from generic.models import CidUser, CidUserGroup

@pytest.fixture
def logged_in_user(db):
    user = User.objects.create_user(username="user1", password="password", email="user1@test.com")
    return user

@pytest.fixture
def logged_in_client(logged_in_user):
    client = Client()
    client.force_login(logged_in_user)
    return client

@pytest.fixture
def anonymous_client(db):
    return Client()

@pytest.fixture
def exam_data(db):
    exam = AnatomyExam.objects.create(
        name="Anatomy Exam Test",
        exam_mode=True,
        active=True,
        open_access=False,
        exam_open_access=True,  # allows open access so we don't have to populate cid/user lists for candidate test
    )
    qtype = QuestionType.objects.create(id=1, text="Test Type")
    structure = Structure.objects.create(name="Test Structure")
    question = AnatomyQuestion.objects.create(
        question_type=qtype,
        structure=structure,
        image="test.jpg",
        description="Test Question description",
    )
    question.exams.set([exam])
    return exam, question

@pytest.mark.django_db
def test_authenticated_user_submit_success(logged_in_client, logged_in_user, exam_data):
    exam, question = exam_data
    payload = {
        "eid": f"anatomy/{exam.pk}",
        "cid": f"u-{logged_in_user.pk}",
        "start_time": timezone.now().timestamp(),
        "answers": json.dumps([
            {
                "eid": f"anatomy/{exam.pk}",
                "cid": f"u-{logged_in_user.pk}",
                "qid": question.pk,
                "ans": "Normal",
                "qidn": "1",
            }
        ])
    }
    response = logged_in_client.post(reverse("global_exam_answers_submit"), payload)
    assert response.status_code == 200
    data = response.json()
    assert data["success"] is True
    assert data["question_count"] == 1

@pytest.mark.django_db
def test_authenticated_user_submit_mismatch_other_user(logged_in_client, logged_in_user, exam_data):
    exam, question = exam_data
    payload = {
        "eid": f"anatomy/{exam.pk}",
        "cid": f"u-{logged_in_user.pk + 999}", # root cid doesn't match authenticated user
        "start_time": timezone.now().timestamp(),
        "answers": json.dumps([
            {
                "eid": f"anatomy/{exam.pk}",
                "cid": f"u-{logged_in_user.pk}",
                "qid": question.pk,
                "ans": "Normal",
                "qidn": "1",
            }
        ])
    }
    response = logged_in_client.post(reverse("global_exam_answers_submit"), payload)
    assert response.status_code == 200
    data = response.json()
    assert data["success"] is False
    assert "user / cid mismatch" in data["error"]

@pytest.mark.django_db
def test_authenticated_user_submit_mismatch_answer_level(logged_in_client, logged_in_user, exam_data):
    exam, question = exam_data
    payload = {
        "eid": f"anatomy/{exam.pk}",
        "cid": f"u-{logged_in_user.pk}",
        "start_time": timezone.now().timestamp(),
        "answers": json.dumps([
            {
                "eid": f"anatomy/{exam.pk}",
                "cid": f"u-{logged_in_user.pk + 999}", # answer level cid doesn't match authenticated user
                "qid": question.pk,
                "ans": "Normal",
                "qidn": "1",
            }
        ])
    }
    response = logged_in_client.post(reverse("global_exam_answers_submit"), payload)
    assert response.status_code == 200
    data = response.json()
    assert data["success"] is False
    assert "user / uid mismatch" in data["error"]

@pytest.mark.django_db
def test_anonymous_user_submit_user_prefix_fails(anonymous_client, exam_data):
    exam, question = exam_data
    payload = {
        "eid": f"anatomy/{exam.pk}",
        "cid": "u-1", # prefix u- is forbidden for anonymous users
        "start_time": timezone.now().timestamp(),
        "answers": json.dumps([
            {
                "eid": f"anatomy/{exam.pk}",
                "cid": "u-1",
                "qid": question.pk,
                "ans": "Normal",
                "qidn": "1",
            }
        ])
    }
    response = anonymous_client.post(reverse("global_exam_answers_submit"), payload)
    assert response.status_code == 200
    data = response.json()
    assert data["success"] is False
    assert "authentication required" in data["error"]

@pytest.mark.django_db
def test_candidate_submit_success(anonymous_client, exam_data):
    exam, question = exam_data
    cid_user = CidUser.objects.create(
        cid=8888,
        name="Candidate 1",
        email="candidate1@test.com",
        passcode="candidatepass",
    )
    # Associate CID user with the exam
    exam.exam_open_access = False
    group = CidUserGroup.objects.create(name="Test Group")
    group.ciduser_set.add(cid_user)
    exam.cid_user_groups.add(group)
    exam.valid_cid_users.add(cid_user)

    payload = {
        "eid": f"anatomy/{exam.pk}",
        "cid": "8888",
        "start_time": timezone.now().timestamp(),
        "answers": json.dumps([
            {
                "eid": f"anatomy/{exam.pk}",
                "cid": "8888",
                "passcode": "candidatepass",
                "qid": question.pk,
                "ans": "Normal",
                "qidn": "1",
            }
        ])
    }
    response = anonymous_client.post(reverse("global_exam_answers_submit"), payload)
    assert response.status_code == 200
    data = response.json()
    assert data["success"] is True
    assert data["question_count"] == 1


@pytest.fixture
def cid_manager_client(db):
    from django.contrib.auth.models import Group
    user = User.objects.create_user(username="manager1", password="password", email="manager1@test.com")
    g, _ = Group.objects.get_or_create(name="cid_user_manager")
    user.groups.add(g)
    client = Client()
    client.force_login(user)
    return client


@pytest.mark.django_db
def test_import_exam_answers_unauthorized(anonymous_client):
    # Anonymous or non-manager users should be redirected or blocked
    url = reverse("generic:import_exam_answers")
    response = anonymous_client.get(url)
    assert response.status_code == 403


@pytest.mark.django_db
def test_import_exam_answers_get(cid_manager_client):
    url = reverse("generic:import_exam_answers")
    response = cid_manager_client.get(url)
    assert response.status_code == 200
    assert b"Import Exam Answers" in response.content


@pytest.mark.django_db
def test_import_exam_answers_post_success(cid_manager_client, exam_data, logged_in_user):
    exam, question = exam_data
    from io import BytesIO
    from anatomy.models import UserAnswer as AnatomyUserAnswer

    import_data = {
        "eid": f"anatomy/{exam.pk}",
        "cid": f"u-{logged_in_user.pk}",
        "start_time": timezone.now().timestamp(),
        "answers": [
            {
                "qid": question.pk,
                "ans": "Imported Test Answer",
                "qidn": "1",
            }
        ]
    }
    
    file_content = json.dumps(import_data).encode("utf-8")
    answers_file = BytesIO(file_content)
    answers_file.name = "backup.json"
    
    url = reverse("generic:import_exam_answers")
    
    # 1. First stage: Upload file -> renders preview
    response = cid_manager_client.post(url, {"answers_file": answers_file}, format="multipart")
    assert response.status_code == 200
    assert b"Preview Import" in response.content
    assert b"Imported Test Answer" in response.content
    
    # Check session has pending data
    assert cid_manager_client.session["pending_import_data"] == import_data
    
    # 2. Second stage: Confirm import
    response2 = cid_manager_client.post(url, {"confirm": "1"})
    assert response2.status_code == 200
    assert b"Import Successful" in response2.content
    assert b"1" in response2.content # created_count count
    
    # Verify answers were saved to DB
    answers = AnatomyUserAnswer.objects.filter(exam=exam, user=logged_in_user)
    assert answers.count() == 1
    assert answers.first().answer == "Imported Test Answer"


@pytest.mark.django_db
def test_answer_history_and_compacting(cid_manager_client, exam_data, logged_in_user):
    exam, question = exam_data
    from anatomy.models import UserAnswer as AnatomyUserAnswer
    import reversion
    
    # 1. Create an answer within a revision context
    with reversion.create_revision():
        ans = AnatomyUserAnswer.objects.create(
            exam=exam,
            question=question,
            user=logged_in_user,
            answer="Version 1",
        )
        reversion.set_comment("Initial answer")
        
    # 2. Modify the answer within a revision context
    with reversion.create_revision():
        ans.answer = "Version 2"
        ans.save()
        reversion.set_comment("Correction")
        
    url = reverse("generic:view_answer_history", kwargs={
        "app_name": "anatomy",
        "model_name": "UserAnswer",
        "pk": ans.pk,
    })
    
    # Check GET revision list
    response = cid_manager_client.get(url)
    assert response.status_code == 200
    assert b"Version 1" in response.content
    assert b"Version 2" in response.content
    assert b"Initial answer" in response.content
    
    # Test that compact_single is forbidden
    response_compact_single = cid_manager_client.post(url, {"action": "compact_single"})
    assert response_compact_single.status_code == 403 # forbidden/denied
    
    # Test Compact Bulk action is forbidden for non-admin/non-staff
    response_compact_bulk_fail = cid_manager_client.post(url, {"action": "compact_bulk"})
    assert response_compact_bulk_fail.status_code == 403
    
    # Elevate the user to superuser/admin to test successful bulk compaction
    from django.contrib.auth import get_user_model
    User = get_user_model()
    manager_user = User.objects.get(username="manager1")
    manager_user.is_superuser = True
    manager_user.save()
    
    # Test Compact Bulk action (exam-wide compaction) succeeds for admin
    response_compact = cid_manager_client.post(url, {"action": "compact_bulk"})
    assert response_compact.status_code == 302 # redirect
    
    # Verify only 1 version remains for our answer object
    from reversion.models import Version
    versions = Version.objects.get_for_object(ans)
    assert versions.count() == 1
    assert versions.first().field_dict["answer"] == "Version 2"


