"""Tests for Grade parsing with missing optional fields.

Some Pronote instances (e.g. 0941093c.index-education.net) do not include
fields like noteMax, noteMin, coefficient, etc. in their grade JSON data.
These tests verify that Grade objects can be constructed without crashing
when those fields are absent.
"""

import unittest
from unittest.mock import MagicMock

from pronotepy.dataClasses import Grade, Period


def _make_grade_json(
    include_noteMax: bool = True,
    include_noteMin: bool = True,
    include_coefficient: bool = True,
    include_commentaire: bool = True,
    include_estBonus: bool = True,
    include_estFacultatif: bool = True,
    include_estRamenerSur20: bool = True,
    include_moyenne: bool = True,
) -> dict:
    """Build a grade JSON dict with optional fields toggled on/off."""
    # Minimal required fields
    json_dict: dict = {
        "N": "001",
        "note": {"V": "15"},
        "bareme": {"V": "20"},
        "baremeParDefaut": {"V": "20"},
        "date": {"V": "15/01/2026"},
        "service": {"V": {"N": "101", "L": "MATHEMATIQUES", "estServiceGroupe": False}},
        "periode": {"V": {"N": "test_period_id"}},
    }
    if include_noteMax:
        json_dict["noteMax"] = {"V": "19"}
    if include_noteMin:
        json_dict["noteMin"] = {"V": "5"}
    if include_coefficient:
        json_dict["coefficient"] = "1"
    if include_commentaire:
        json_dict["commentaire"] = "Good work"
    if include_estBonus:
        json_dict["estBonus"] = False
    if include_estFacultatif:
        json_dict["estFacultatif"] = False
    if include_estRamenerSur20:
        json_dict["estRamenerSur20"] = False
    if include_moyenne:
        json_dict["moyenne"] = {"V": "12"}
    return json_dict


class TestGradeMissingFields(unittest.TestCase):
    """Test that Grade parsing handles missing optional fields gracefully."""

    @classmethod
    def setUpClass(cls) -> None:
        # Create a mock Period instance so the period resolver can find it
        mock_period = MagicMock(spec=Period)
        mock_period.id = "test_period_id"
        Period.instances.add(mock_period)

    @classmethod
    def tearDownClass(cls) -> None:
        # Clean up mock period instances
        Period.instances = {p for p in Period.instances if not isinstance(p, MagicMock)}

    def test_all_fields_present(self) -> None:
        """Parsing works when all fields are present."""
        json_dict = _make_grade_json()
        grade = Grade(json_dict)
        self.assertEqual(grade.grade, "15")
        self.assertEqual(grade.max, "19")
        self.assertEqual(grade.min, "5")
        self.assertEqual(grade.coefficient, "1")
        self.assertEqual(grade.comment, "Good work")
        self.assertFalse(grade.is_bonus)
        self.assertFalse(grade.is_optionnal)
        self.assertFalse(grade.is_out_of_20)

    def test_missing_noteMax(self) -> None:
        """Parsing does not crash when noteMax is missing."""
        json_dict = _make_grade_json(include_noteMax=False)
        grade = Grade(json_dict)
        self.assertIsNone(grade.max)
        # Other fields should still be parsed correctly
        self.assertEqual(grade.grade, "15")
        self.assertEqual(grade.min, "5")

    def test_missing_noteMin(self) -> None:
        """Parsing does not crash when noteMin is missing."""
        json_dict = _make_grade_json(include_noteMin=False)
        grade = Grade(json_dict)
        self.assertIsNone(grade.min)

    def test_missing_coefficient(self) -> None:
        """Parsing does not crash when coefficient is missing."""
        json_dict = _make_grade_json(include_coefficient=False)
        grade = Grade(json_dict)
        self.assertIsNone(grade.coefficient)

    def test_missing_commentaire(self) -> None:
        """Parsing does not crash when commentaire is missing."""
        json_dict = _make_grade_json(include_commentaire=False)
        grade = Grade(json_dict)
        self.assertIsNone(grade.comment)

    def test_missing_estBonus(self) -> None:
        """Parsing does not crash when estBonus is missing."""
        json_dict = _make_grade_json(include_estBonus=False)
        grade = Grade(json_dict)
        self.assertFalse(grade.is_bonus)

    def test_missing_estFacultatif(self) -> None:
        """Parsing does not crash when estFacultatif is missing."""
        json_dict = _make_grade_json(include_estFacultatif=False)
        grade = Grade(json_dict)
        self.assertFalse(grade.is_optionnal)

    def test_missing_estRamenerSur20(self) -> None:
        """Parsing does not crash when estRamenerSur20 is missing."""
        json_dict = _make_grade_json(include_estRamenerSur20=False)
        grade = Grade(json_dict)
        self.assertFalse(grade.is_out_of_20)

    def test_missing_moyenne(self) -> None:
        """Parsing does not crash when moyenne is missing."""
        json_dict = _make_grade_json(include_moyenne=False)
        grade = Grade(json_dict)
        self.assertIsNone(grade.average)

    def test_all_optional_fields_missing(self) -> None:
        """Parsing does not crash when ALL optional fields are missing.

        This simulates the worst case scenario where a Pronote instance
        returns only the bare minimum grade data.
        """
        json_dict = _make_grade_json(
            include_noteMax=False,
            include_noteMin=False,
            include_coefficient=False,
            include_commentaire=False,
            include_estBonus=False,
            include_estFacultatif=False,
            include_estRamenerSur20=False,
            include_moyenne=False,
        )
        grade = Grade(json_dict)
        self.assertEqual(grade.grade, "15")
        self.assertEqual(grade.out_of, "20")
        self.assertIsNone(grade.max)
        self.assertIsNone(grade.min)
        self.assertIsNone(grade.coefficient)
        self.assertIsNone(grade.comment)
        self.assertFalse(grade.is_bonus)
        self.assertFalse(grade.is_optionnal)
        self.assertFalse(grade.is_out_of_20)
        self.assertIsNone(grade.average)


if __name__ == "__main__":
    unittest.main()
