"""QuizTrainer 额外功能测试 - 提高覆盖率。"""

import json
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Generator
from unittest.mock import MagicMock, patch

import pytest

# 添加项目路径
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "pytola-office"))

from pytola_office.quiztrainer.core import DataManager, QuizManager, QuizSession
from pytola_office.quiztrainer.models import (
    AnswerRecord,
    Difficulty,
    EssayQuestion,
    QuestionType,
    UserData,
)


class TestDataManagerEdgeCases:
    """数据管理器边界条件测试."""

    @pytest.fixture
    def temp_dir(self) -> Generator[Path, None, None]:
        """创建临时目录用于测试。"""
        temp_path = tempfile.mkdtemp()
        yield temp_path
        shutil.rmtree(temp_path, ignore_errors=True)

    @pytest.fixture
    def data_manager(self, temp_dir: Path) -> DataManager:
        """创建测试用的数据管理器。"""
        with patch.object(DataManager, "DATA_DIR", Path(temp_dir)):
            dm = DataManager()
            dm.DATA_DIR = Path(temp_dir)
            dm.QUESTIONS_DIR = dm.DATA_DIR / "questions"
            dm.USER_DATA_DIR = dm.DATA_DIR / "user_data"
            dm._ensure_directories()
            return dm

    def test_load_json_with_invalid_file(self, data_manager: DataManager, temp_dir: Path) -> None:
        """测试加载无效的 JSON 文件."""
        # 创建一个无效的 JSON 文件
        invalid_file = Path(temp_dir) / "invalid.json"
        invalid_file.write_text("{ invalid json content", encoding="utf-8")

        # 应该返回空字典而不是抛出异常
        result = data_manager._load_json(invalid_file)
        assert result == {}

    def test_load_json_with_nonexistent_file(self, data_manager: DataManager) -> None:
        """测试加载不存在的文件."""
        result = data_manager._load_json(Path("nonexistent.json"))
        assert result == {}

    def test_save_json_with_unicode(self, data_manager: DataManager, temp_dir: Path) -> None:
        """测试保存包含 Unicode 的 JSON."""
        test_data = {"question": "测试题目", "options": ["选项 A", "选项 B"]}
        test_file = Path(temp_dir) / "unicode_test.json"

        data_manager._save_json(test_file, test_data)

        # 验证文件存在且内容正确
        assert test_file.exists()
        loaded = json.loads(test_file.read_text(encoding="utf-8"))
        assert loaded["question"] == "测试题目"

    def test_get_categories(self, data_manager: DataManager) -> None:
        """测试获取题目分类."""
        # 添加不同分类的题目
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="Python 基础",
                difficulty=Difficulty.EASY,
                question="Python 是什么?",
                answer="A",
                options=["A. 语言", "B. 工具"],
            ),
            ChoiceQuestion(
                id="q2",
                type=QuestionType.CHOICE,
                category="Python 基础",
                difficulty=Difficulty.MEDIUM,
                question="Python 版本?",
                answer="A",
                options=["A. 3.x", "B. 2.x"],
            ),
            ChoiceQuestion(
                id="q3",
                type=QuestionType.CHOICE,
                category="数据结构",
                difficulty=Difficulty.HARD,
                question="列表特点?",
                answer="A",
                options=["A. 有序", "B. 无序"],
            ),
        ]

        for q in questions:
            data_manager.add_question(q, "category_test")

        categories = data_manager.get_categories()
        assert len(categories) == 2

        # 验证 Python 基础分类
        python_cat = next((c for c in categories if c.name == "Python 基础"), None)
        assert python_cat is not None
        assert python_cat.question_count == 2
        assert python_cat.difficulty_dist["easy"] == 1
        assert python_cat.difficulty_dist["medium"] == 1

        # 验证数据结构分类
        data_cat = next((c for c in categories if c.name == "数据结构"), None)
        assert data_cat is not None
        assert data_cat.question_count == 1
        assert data_cat.difficulty_dist["hard"] == 1

    def test_current_question_none(self, temp_dir: str) -> None:
        """测试当前题目为 None 的情况."""
        # 空题目列表
        session = QuizSession(questions=[], mode="practice")
        assert session.current_question is None

    def test_current_question_out_of_bounds(self, temp_dir: str) -> None:
        """测试索引超出范围时当前题目为 None."""
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="测试",
                difficulty=Difficulty.EASY,
                question="测试",
                answer="A",
                options=["A", "B"],
            ),
        ]
        session = QuizSession(questions=questions, mode="practice")
        session.current_index = 10  # 超出范围
        assert session.current_question is None

    def test_submit_answer_no_current_question(self) -> None:
        """测试没有当前题目时提交答案抛出异常."""
        session = QuizSession(questions=[], mode="practice")

        with pytest.raises(ValueError, match="没有当前题目"):
            session.submit_answer("A")

    def test_next_question_at_end(self) -> None:
        """测试在最后一题时下一题返回 None."""
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="测试",
                difficulty=Difficulty.EASY,
                question="测试",
                answer="A",
                options=["A", "B"],
            ),
        ]
        session = QuizSession(questions=questions, mode="practice")
        result = session.next_question()
        assert result is None

    def test_prev_question_at_start(self) -> None:
        """测试在第一题时上一题返回 None."""
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="测试",
                difficulty=Difficulty.EASY,
                question="测试",
                answer="A",
                options=["A", "B"],
            ),
        ]
        session = QuizSession(questions=questions, mode="practice")
        result = session.prev_question()
        assert result is None

    def test_jump_to_invalid_index(self) -> None:
        """测试跳转到无效索引返回 None."""
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="测试",
                difficulty=Difficulty.EASY,
                question="测试",
                answer="A",
                options=["A", "B"],
            ),
        ]
        session = QuizSession(questions=questions, mode="practice")

        # 跳转到负数索引
        result = session.jump_to(-1)
        assert result is None

        # 跳转到超出范围的索引
        result = session.jump_to(10)
        assert result is None


class TestQuizManagerAdvanced:
    """题库管理器高级功能测试."""

    @pytest.fixture
    def temp_dir(self) -> Generator[Path, None, None]:
        """创建临时目录。"""
        temp_path = tempfile.mkdtemp()
        yield temp_path
        shutil.rmtree(temp_path, ignore_errors=True)

    @pytest.fixture
    def quiz_manager(self, temp_dir: Path) -> QuizManager:
        """创建测试用的题库管理器。"""
        dm = DataManager()
        dm.DATA_DIR = Path(temp_dir)
        dm.QUESTIONS_DIR = dm.DATA_DIR / "questions"
        dm.USER_DATA_DIR = dm.DATA_DIR / "user_data"
        dm._ensure_directories()
        return QuizManager(dm)

    def test_create_question_essay(self, quiz_manager: QuizManager) -> None:
        """测试创建问答题."""
        q = quiz_manager.create_question(
            q_type="essay",
            question="请简述 Python 的特点",
            answer="Python 是一种高级编程语言, 具有简洁清晰、易读易写等特点",
            explanation="参考答案要点",
        )
        assert isinstance(q, EssayQuestion)
        assert q.type == QuestionType.ESSAY
        assert "Python" in q.answer

    def test_filter_questions_by_category(self, quiz_manager: QuizManager) -> None:
        """测试按分类筛选题目."""
        # 添加不同分类的题目
        quiz_manager.data_manager.add_question(
            quiz_manager.create_question(
                q_type="choice",
                question="Python 题",
                answer="A",
                category="Python",
                options=["A", "B", "C", "D"],
            ),
            "category_filter",
        )
        quiz_manager.data_manager.add_question(
            quiz_manager.create_question(
                q_type="choice",
                question="数学题",
                answer="A",
                category="Math",
                options=["A", "B", "C", "D"],
            ),
            "category_filter",
        )

        # 筛选 Python 分类
        python_questions = quiz_manager.filter_questions(
            bank_name="category_filter",
            category="Python",
        )
        assert len(python_questions) == 1
        assert python_questions[0].category == "Python"

    def test_filter_questions_with_limit(self, quiz_manager: QuizManager) -> None:
        """测试限制数量的筛选."""
        # 添加多个题目
        for i in range(10):
            quiz_manager.data_manager.add_question(
                quiz_manager.create_question(
                    q_type="choice",
                    question=f"题目{i}",
                    answer="A",
                    options=["A", "B", "C", "D"],
                ),
                "limit_test",
            )

        # 限制返回 3 个
        limited = quiz_manager.filter_questions(
            bank_name="limit_test",
            limit=3,
        )
        assert len(limited) == 3

    def test_start_quiz(self, quiz_manager: QuizManager) -> None:
        """测试开始答题功能."""
        # 添加一些题目
        for i in range(5):
            quiz_manager.data_manager.add_question(
                quiz_manager.create_question(
                    q_type="choice",
                    question=f"测试题目{i}",
                    answer="A",
                    category="Test",
                    difficulty="easy",
                    options=["A", "B", "C", "D"],
                ),
                "quiz_test",
            )

        # 开始答题
        session = quiz_manager.start_quiz(
            bank_name="quiz_test",
            count=3,
            mode="practice",
            shuffle=False,
            user_id="test_user",
        )

        assert isinstance(session, QuizSession)
        assert len(session.questions) == 3
        assert session.mode == "practice"
        assert session.user_id == "test_user"

    def test_start_quiz_with_filters(self, quiz_manager: QuizManager) -> None:
        """测试带筛选条件开始答题."""
        # 添加不同类型和难度的题目
        quiz_manager.data_manager.add_question(
            quiz_manager.create_question(
                q_type="choice",
                question="简单选择题",
                answer="A",
                difficulty="easy",
                options=["A", "B", "C", "D"],
            ),
            "filter_quiz",
        )
        quiz_manager.data_manager.add_question(
            quiz_manager.create_question(
                q_type="truefalse",
                question="简单判断题",
                answer=True,
                difficulty="easy",
            ),
            "filter_quiz",
        )
        quiz_manager.data_manager.add_question(
            quiz_manager.create_question(
                q_type="choice",
                question="困难选择题",
                answer="A",
                difficulty="hard",
                options=["A", "B", "C", "D"],
            ),
            "filter_quiz",
        )

        # 只选择简单题
        session = quiz_manager.start_quiz(
            bank_name="filter_quiz",
            difficulty="easy",
            count=5,
            shuffle=False,
        )

        assert len(session.questions) == 2
        assert all(q.difficulty == Difficulty.EASY for q in session.questions)

    def test_get_statistics_with_data(self, quiz_manager: QuizManager) -> None:
        """测试获取有数据的统计信息."""
        # 添加一些题目和用户答题记录
        quiz_manager.data_manager.add_question(
            quiz_manager.create_question(
                q_type="choice",
                question="测试题",
                answer="A",
                difficulty="easy",
                options=["A", "B", "C", "D"],
            ),
            "stats_test",
        )

        # 创建用户数据
        user_data = UserData(user_id="stats_user")
        user_data.history = [
            AnswerRecord(question_id="q1", user_answer="A", is_correct=True),
            AnswerRecord(question_id="q2", user_answer="B", is_correct=False),
            AnswerRecord(question_id="q3", user_answer="C", is_correct=True),
        ]
        user_data.category_stats = {
            "Python": {"total": 2, "correct": 1},
            "Math": {"total": 1, "correct": 1},
        }
        quiz_manager.data_manager.save_user_data(user_data)

        # 获取统计信息
        stats = quiz_manager.get_statistics(user_id="stats_user")

        assert stats["total_questions"] == 3
        assert stats["correct_count"] == 2
        assert stats["accuracy"] == pytest.approx(66.7, rel=0.1)
        assert "category_stats" in stats
        assert "difficulty_stats" in stats
        assert len(stats["recent_history"]) == 3


class TestBaseQuestionAdvanced:
    """基础题目高级功能测试."""

    def test_check_answer_with_list(self) -> None:
        """测试列表答案的检查."""
        from pytola_office.quiztrainer.models import BaseQuestion

        q = BaseQuestion(
            id="q1",
            type=QuestionType.CHOICE,
            category="测试",
            difficulty=Difficulty.MEDIUM,
            question="多选题",
            answer=["A", "B", "C"],  # 多个正确答案
            explanation="多选测试",
        )

        # 任何一个答案都应该被认为是正确的
        assert q.check_answer("A") is True
        assert q.check_answer("B") is True
        assert q.check_answer("C") is True
        assert q.check_answer("D") is False

    def test_base_question_from_dict_essay(self) -> None:
        """测试从字典创建问答题."""
        data = {
            "id": "essay1",
            "type": "essay",
            "category": "论述题",
            "difficulty": "hard",
            "question": "请论述人工智能的影响",
            "answer": "人工智能对社会有深远影响...",
            "explanation": "参考要点",
        }

        q = EssayQuestion.from_dict(data)
        assert q.id == "essay1"
        assert q.type == QuestionType.ESSAY
        assert "人工智能" in q.question


class TestUserDataFromDict:
    """用户数据从字典创建测试."""

    def test_user_data_from_dict_with_empty_data(self) -> None:
        """测试从空字典创建用户数据."""
        data = {}
        user_data = UserData.from_dict(data)
        assert user_data.user_id == "default"
        assert user_data.history == []
        assert user_data.category_stats == {}

    def test_user_data_from_dict_with_history(self) -> None:
        """测试从带历史记录的字典创建用户数据."""
        data = {
            "user_id": "test_user",
            "history": [
                {
                    "question_id": "q1",
                    "user_answer": "A",
                    "is_correct": True,
                    "timestamp": "2024-01-01T00:00:00",
                    "time_spent": 30,
                },
                {
                    "question_id": "q2",
                    "user_answer": "B",
                    "is_correct": False,
                },
            ],
            "category_stats": {
                "Python": {"total": 1, "correct": 1},
            },
        }

        user_data = UserData.from_dict(data)
        assert user_data.user_id == "test_user"
        assert len(user_data.history) == 2
        assert user_data.history[0].question_id == "q1"
        assert user_data.history[0].time_spent == 30
        assert "Python" in user_data.category_stats


class TestQuizSessionWithMock:
    """使用 Mock 测试 QuizSession."""

    def test_update_user_data_in_practice_mode(self) -> None:
        """测试练习模式下更新用户数据."""
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="Python",
                difficulty=Difficulty.EASY,
                question="测试",
                answer="A",
                options=["A", "B"],
            ),
        ]

        # 创建 mock 的 data_manager
        mock_dm = MagicMock()
        mock_user_data = UserData(user_id="test")
        mock_dm.load_user_data.return_value = mock_user_data

        session = QuizSession(
            questions=questions,
            mode="practice",
            user_id="test",
            data_manager=mock_dm,
        )

        # 提交答案
        session.submit_answer("A")

        # 验证用户数据被保存
        assert mock_dm.save_user_data.called

    def test_no_user_data_update_in_exam_mode(self) -> None:
        """测试考试模式下不更新用户数据."""
        from pytola_office.quiztrainer.models import ChoiceQuestion

        questions = [
            ChoiceQuestion(
                id="q1",
                type=QuestionType.CHOICE,
                category="Python",
                difficulty=Difficulty.EASY,
                question="测试",
                answer="A",
                options=["A", "B"],
            ),
        ]

        mock_dm = MagicMock()

        session = QuizSession(
            questions=questions,
            mode="exam",  # 考试模式
            user_id="test",
            data_manager=mock_dm,
        )

        # 提交答案
        session.submit_answer("A")

        # 验证用户数据没有被保存
        mock_dm.save_user_data.assert_not_called()


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
