"""Tests for quizbase module."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import pytest

from pytola.office.quizbase.quizbase import (
    EssayQuestion,
    FillBlankQuestion,
    MultipleChoiceQuestion,
    Question,
    QuestionType,
    QuizResult,
    QuizSession,
    TrueFalseQuestion,
    create_sample_quiz_data,
)


@pytest.fixture
def sample_quiz_json(tmp_path: Path) -> Path:
    """Create a sample quiz JSON file."""
    quiz_file = tmp_path / "quiz.json"
    quiz_data = {
        "questions": [
            {
                "question_id": "mc1",
                "question_type": "multiple_choice",
                "question_text": "What is 2+2?",
                "options": ["3", "4", "5", "6"],
                "correct_answer": 1,
                "allow_multiple": False,
                "points": 1.0,
            },
            {
                "question_id": "mc2",
                "question_type": "multiple_choice",
                "question_text": "Select prime numbers",
                "options": ["2", "4", "5", "6"],
                "correct_answer": [0, 2],
                "allow_multiple": True,
                "points": 2.0,
            },
            {
                "question_id": "fb1",
                "question_type": "fill_blank",
                "question_text": "The capital of France is _____.",
                "correct_answers": ["Paris"],
                "case_sensitive": False,
                "points": 1.0,
            },
            {
                "question_id": "tf1",
                "question_type": "true_false",
                "question_text": "Python is a compiled language.",
                "correct_answer": False,
                "points": 1.0,
            },
            {
                "question_id": "es1",
                "question_type": "essay",
                "question_text": "Explain OOP.",
                "model_answer": "Object-oriented programming...",
                "keywords": ["objects", "classes", "inheritance"],
                "points": 5.0,
            },
        ]
    }
    with open(quiz_file, "w", encoding="utf-8") as f:
        json.dump(quiz_data, f)
    return quiz_file


class TestMultipleChoiceQuestion:
    """Tests for MultipleChoiceQuestion."""

    def test_single_correct_answer(self):
        """Test checking single correct answer."""
        question = MultipleChoiceQuestion(
            question_id="mc1",
            question_type=QuestionType.MULTIPLE_CHOICE,
            question_text="What is 2+2?",
            options=["3", "4", "5", "6"],
            correct_answer=1,
            allow_multiple=False,
        )

        is_correct, explanation = question.check_answer(1)
        assert is_correct
        assert "Correct" in explanation

        is_correct, explanation = question.check_answer(0)
        assert not is_correct
        assert "Incorrect" in explanation
        assert "4" in explanation

    def test_multiple_correct_answers(self):
        """Test checking multiple correct answers."""
        question = MultipleChoiceQuestion(
            question_id="mc2",
            question_type=QuestionType.MULTIPLE_CHOICE,
            question_text="Select prime numbers",
            options=["2", "4", "5", "6"],
            correct_answer=[0, 2],
            allow_multiple=True,
        )

        is_correct, explanation = question.check_answer([0, 2])
        assert is_correct
        assert "Correct" in explanation

        is_correct, explanation = question.check_answer([0])
        assert not is_correct

    def test_to_dict(self):
        """Test converting question to dictionary."""
        question = MultipleChoiceQuestion(
            question_id="mc1",
            question_type=QuestionType.MULTIPLE_CHOICE,
            question_text="What is 2+2?",
            options=["3", "4", "5", "6"],
            correct_answer=1,
            allow_multiple=False,
        )

        data = question.to_dict()
        assert data["question_id"] == "mc1"
        assert data["question_type"] == "multiple_choice"
        assert data["options"] == ["3", "4", "5", "6"]
        assert data["correct_answer"] == 1

    def test_from_dict(self):
        """Test creating question from dictionary."""
        data = {
            "question_id": "mc1",
            "question_type": "multiple_choice",
            "question_text": "What is 2+2?",
            "options": ["3", "4", "5", "6"],
            "correct_answer": 1,
            "allow_multiple": False,
            "points": 1.0,
        }

        question = MultipleChoiceQuestion.from_dict(data)
        assert question.question_id == "mc1"
        assert question.correct_answer == 1
        assert question.points == 1.0


class TestFillBlankQuestion:
    """Tests for FillBlankQuestion."""

    def test_case_insensitive_answer(self):
        """Test checking case-insensitive answer."""
        question = FillBlankQuestion(
            question_id="fb1",
            question_type=QuestionType.FILL_BLANK,
            question_text="The capital of France is _____.",
            correct_answers=["Paris"],
            case_sensitive=False,
        )

        is_correct, _ = question.check_answer("Paris")
        assert is_correct

        is_correct, _ = question.check_answer("paris")
        assert is_correct

        is_correct, _ = question.check_answer("PARIS")
        assert is_correct

    def test_case_sensitive_answer(self):
        """Test checking case-sensitive answer."""
        question = FillBlankQuestion(
            question_id="fb1",
            question_type=QuestionType.FILL_BLANK,
            question_text="The capital of France is _____.",
            correct_answers=["Paris"],
            case_sensitive=True,
        )

        is_correct, _ = question.check_answer("Paris")
        assert is_correct

        is_correct, _ = question.check_answer("paris")
        assert not is_correct

    def test_multiple_valid_answers(self):
        """Test checking multiple valid answers."""
        question = FillBlankQuestion(
            question_id="fb1",
            question_type=QuestionType.FILL_BLANK,
            question_text="Select colors",
            correct_answers=["Red", "Blue", "Green"],
            case_sensitive=False,
        )

        is_correct, _ = question.check_answer("red")
        assert is_correct

        is_correct, _ = question.check_answer("blue")
        assert is_correct

    def test_to_dict_and_from_dict(self):
        """Test serialization and deserialization."""
        question = FillBlankQuestion(
            question_id="fb1",
            question_type=QuestionType.FILL_BLANK,
            question_text="The capital of France is _____.",
            correct_answers=["Paris"],
            case_sensitive=False,
            points=2.0,
        )

        data = question.to_dict()
        assert data["correct_answers"] == ["Paris"]
        assert data["case_sensitive"] is False

        new_question = FillBlankQuestion.from_dict(data)
        assert new_question.question_id == "fb1"
        assert new_question.correct_answers == ["Paris"]


class TestTrueFalseQuestion:
    """Tests for TrueFalseQuestion."""

    def test_boolean_answer(self):
        """Test checking boolean answer."""
        question = TrueFalseQuestion(
            question_id="tf1",
            question_type=QuestionType.TRUE_FALSE,
            question_text="Python is a compiled language.",
            correct_answer=False,
        )

        is_correct, _ = question.check_answer(False)
        assert is_correct

        is_correct, _ = question.check_answer(True)
        assert not is_correct

    def test_string_answer(self):
        """Test checking string answer."""
        question = TrueFalseQuestion(
            question_id="tf1",
            question_type=QuestionType.TRUE_FALSE,
            question_text="Python is a compiled language.",
            correct_answer=False,
        )

        for answer in ["false", "f", "no", "n"]:
            is_correct, _ = question.check_answer(answer)
            assert is_correct

        for answer in ["true", "t", "yes", "y"]:
            is_correct, _ = question.check_answer(answer)
            assert not is_correct

    def test_invalid_string_answer(self):
        """Test invalid string answer."""
        question = TrueFalseQuestion(
            question_id="tf1",
            question_type=QuestionType.TRUE_FALSE,
            question_text="Python is a compiled language.",
            correct_answer=False,
        )

        is_correct, explanation = question.check_answer("invalid")
        assert not is_correct
        assert "Invalid" in explanation


class TestEssayQuestion:
    """Tests for EssayQuestion."""

    def test_keyword_validation(self):
        """Test keyword-based validation."""
        question = EssayQuestion(
            question_id="es1",
            question_type=QuestionType.ESSAY,
            question_text="Explain OOP.",
            model_answer="Object-oriented programming...",
            keywords=["objects", "classes", "inheritance"],
        )

        # All keywords present
        is_correct, _ = question.check_answer("OOP uses objects and classes with inheritance.")
        assert is_correct

        # Partial keywords
        is_correct, _ = question.check_answer("OOP uses objects and classes.")
        assert is_correct  # 2 out of 3 keywords (>= 50%)

        # No keywords
        is_correct, _ = question.check_answer("I don't know.")
        assert not is_correct

    def test_no_keywords(self):
        """Test essay question without keywords."""
        question = EssayQuestion(
            question_id="es1",
            question_type=QuestionType.ESSAY,
            question_text="Explain OOP.",
            model_answer="Object-oriented programming...",
            keywords=[],
        )

        is_correct, explanation = question.check_answer("Some answer.")
        assert is_correct
        assert "recorded" in explanation

    def test_to_dict_and_from_dict(self):
        """Test serialization and deserialization."""
        question = EssayQuestion(
            question_id="es1",
            question_type=QuestionType.ESSAY,
            question_text="Explain OOP.",
            model_answer="Object-oriented programming...",
            keywords=["objects", "classes", "inheritance"],
            points=5.0,
        )

        data = question.to_dict()
        assert data["model_answer"] == "Object-oriented programming..."
        assert data["keywords"] == ["objects", "classes", "inheritance"]

        new_question = EssayQuestion.from_dict(data)
        assert new_question.question_id == "es1"
        assert new_question.keywords == ["objects", "classes", "inheritance"]


class TestQuestionFactory:
    """Tests for Question factory methods."""

    def test_from_dict_multiple_choice(self):
        """Test creating MultipleChoiceQuestion from dict."""
        data: dict[str, Any] = {
            "question_id": "mc1",
            "question_type": "multiple_choice",
            "question_text": "What is 2+2?",
            "options": ["3", "4", "5", "6"],
            "correct_answer": 1,
            "allow_multiple": False,
        }
        question = Question.from_dict(data)
        assert isinstance(question, MultipleChoiceQuestion)

    def test_from_dict_fill_blank(self):
        """Test creating FillBlankQuestion from dict."""
        data: dict[str, Any] = {
            "question_id": "fb1",
            "question_type": "fill_blank",
            "question_text": "The capital of France is _____.",
            "correct_answers": ["Paris"],
            "case_sensitive": False,
        }
        question = Question.from_dict(data)
        assert isinstance(question, FillBlankQuestion)

    def test_from_dict_true_false(self):
        """Test creating TrueFalseQuestion from dict."""
        data: dict[str, Any] = {
            "question_id": "tf1",
            "question_type": "true_false",
            "question_text": "Python is a compiled language.",
            "correct_answer": False,
        }
        question = Question.from_dict(data)
        assert isinstance(question, TrueFalseQuestion)

    def test_from_dict_essay(self):
        """Test creating EssayQuestion from dict."""
        data: dict[str, Any] = {
            "question_id": "es1",
            "question_type": "essay",
            "question_text": "Explain OOP.",
            "model_answer": "Object-oriented programming...",
            "keywords": ["objects", "classes"],
        }
        question = Question.from_dict(data)
        assert isinstance(question, EssayQuestion)


class TestQuizSession:
    """Tests for QuizSession."""

    def test_load_from_json(self, sample_quiz_json: Path):
        """Test loading questions from JSON file."""
        session = QuizSession()
        session.load_from_json(sample_quiz_json)
        assert session.total_questions == 5

    def test_sequential_order(self, sample_quiz_json: Path):
        """Test sequential question order."""
        session = QuizSession(random_order=False)
        session.load_from_json(sample_quiz_json)

        first_question = session.get_current_question()
        assert first_question
        assert first_question.question_id == "mc1"

        session.submit_answer(1)

        second_question = session.get_current_question()
        assert second_question
        assert second_question.question_id == "mc2"

    def test_random_order(self, sample_quiz_json: Path):
        """Test random question order."""
        session = QuizSession(random_order=True)
        session.load_from_json(sample_quiz_json)

        # Get all question IDs in random order
        question_ids = []
        while not session.is_finished():
            question = session.get_current_question()
            if question:
                question_ids.append(question.question_id)
                session.submit_answer(1 if isinstance(question, MultipleChoiceQuestion) else "test")

        # Verify all questions were answered
        assert len(question_ids) == 5

    def test_submit_answer(self, sample_quiz_json: Path):
        """Test submitting answer."""
        session = QuizSession()
        session.load_from_json(sample_quiz_json)

        question = session.get_current_question()
        assert isinstance(question, MultipleChoiceQuestion)

        result = session.submit_answer(1)  # Correct answer for "What is 2+2?"
        assert result is not None
        assert result.is_correct
        assert result.user_answer == 1

    def test_statistics(self, sample_quiz_json: Path):
        """Test session statistics calculation."""
        session = QuizSession()
        session.load_from_json(sample_quiz_json)

        # Answer all questions
        while not session.is_finished():
            question = session.get_current_question()
            if question:
                if isinstance(question, MultipleChoiceQuestion):
                    session.submit_answer(1 if not question.allow_multiple else [0])
                elif isinstance(question, FillBlankQuestion):
                    session.submit_answer("Paris")
                elif isinstance(question, TrueFalseQuestion):
                    session.submit_answer(True)
                else:
                    session.submit_answer("Some answer")

        summary = session.get_summary()
        assert summary["total_questions"] == 5
        assert summary["answered"] == 5
        assert summary["correct"] > 0 or summary["wrong"] > 0
        assert summary["accuracy"] >= 0.0
        assert summary["accuracy"] <= 100.0

    def test_wrong_answers(self, tmp_path: Path):
        """Test wrong answers collection and saving."""
        quiz_file = tmp_path / "quiz.json"
        quiz_data = {
            "questions": [
                {
                    "question_id": "mc1",
                    "question_type": "multiple_choice",
                    "question_text": "What is 2+2?",
                    "options": ["3", "4", "5"],
                    "correct_answer": 1,
                    "allow_multiple": False,
                },
            ]
        }
        with open(quiz_file, "w", encoding="utf-8") as f:
            json.dump(quiz_data, f)

        session = QuizSession(wrong_answer_file=str(tmp_path / "wrong.json"))
        session.load_from_json(quiz_file)

        # Submit wrong answer
        session.submit_answer(0)

        wrong_answers = session.get_wrong_answers()
        assert len(wrong_answers) == 1
        assert not wrong_answers[0].is_correct

        # Save wrong answers
        session.save_wrong_answers()

        wrong_file = tmp_path / "wrong.json"
        assert wrong_file.exists()

        with open(wrong_file, encoding="utf-8") as f:
            data = json.load(f)
            assert "wrong_answers" in data
            assert len(data["wrong_answers"]) == 1

    def test_reset(self, sample_quiz_json: Path):
        """Test resetting quiz session."""
        session = QuizSession()
        session.load_from_json(sample_quiz_json)

        # Answer one question
        session.submit_answer(1)
        assert len(session.results) == 1

        # Reset
        session.reset()
        assert len(session.results) == 0
        assert session._current_index == 0

    def test_get_current_question_after_finish(self, sample_quiz_json: Path):
        """Test getting current question after quiz is finished."""
        session = QuizSession()
        session.load_from_json(sample_quiz_json)

        # Answer all questions
        while not session.is_finished():
            question = session.get_current_question()
            if question:
                session.submit_answer(1 if isinstance(question, MultipleChoiceQuestion) else "test")

        # Try to get question after finish
        question = session.get_current_question()
        assert question is None


class TestCreateSampleQuizData:
    """Tests for create_sample_quiz_data function."""

    def test_create_sample_quiz(self, tmp_path: Path):
        """Test creating sample quiz data file."""
        output_file = tmp_path / "sample_quiz.json"
        create_sample_quiz_data(output_file)

        assert output_file.exists()

        with open(output_file, encoding="utf-8") as f:
            data = json.load(f)
            assert "title" in data
            assert "questions" in data
            assert len(data["questions"]) == 7

            # Verify all question types are present
            question_types = {q["question_type"] for q in data["questions"]}
            expected_types = {
                "multiple_choice",
                "fill_blank",
                "true_false",
                "essay",
            }
            assert question_types == expected_types


class TestQuizResult:
    """Tests for QuizResult dataclass."""

    def test_to_dict(self):
        """Test converting result to dictionary."""
        question = MultipleChoiceQuestion(
            question_id="mc1",
            question_type=QuestionType.MULTIPLE_CHOICE,
            question_text="What is 2+2?",
            options=["3", "4", "5", "6"],
            correct_answer=1,
            allow_multiple=False,
        )

        result = QuizResult(question=question, user_answer=1, is_correct=True, explanation="Correct!")

        data = result.to_dict()
        assert data["question"]["question_id"] == "mc1"
        assert data["user_answer"] == 1
        assert data["is_correct"] is True
        assert data["explanation"] == "Correct!"
