"""Small in-memory teaching model. No database, login or external service."""
class Student:
    def __init__(self, student_id, name):
        self.student_id = student_id
        self.name = name
        self._enrollments = []

    def enroll(self, course):
        for enrollment in self._enrollments:
            if enrollment.course is course:
                raise ValueError("Already enrolled")
        enrollment = Enrollment(self, course)
        self._enrollments.append(enrollment)
        course._enrollments.append(enrollment)
        return enrollment

    def course_codes(self):
        return [enrollment.course.course_code for enrollment in self._enrollments]


class Course:
    def __init__(self, course_code, title, credits):
        if not isinstance(credits, int) or isinstance(credits, bool) or credits <= 0:
            raise ValueError("Credits must be a positive integer")
        self.course_code = course_code
        self.title = title
        self.credits = credits
        self._enrollments = []

    def student_names(self):
        return [enrollment.student.name for enrollment in self._enrollments]


class Enrollment:
    def __init__(self, student, course):
        self.student = student
        self.course = course
        self._grade = None

    @property
    def grade(self):
        return self._grade

    def set_grade(self, value):
        if value not in {"A", "B", "C", "D", "F"}:
            raise ValueError("Grade must be A, B, C, D or F")
        self._grade = value

ada = Student("S001", "Ada Lovelace")
cs101 = Course("CS101", "Intro to CS", 6)
enrollment = ada.enroll(cs101)
enrollment.set_grade("A")
print(ada.name, ada.course_codes())
print(cs101.course_code, cs101.student_names())
print("Grade:", enrollment.grade)
try:
    ada.enroll(cs101)
except ValueError as error:
    print("Rejected:", error)
print("Enrollment links:", len(ada._enrollments), len(cs101._enrollments))
