from src.domain.student import Student
from src.domain.assignment import Assignment
from src.domain.grade import Grade
from src.errors.errors import *
from datetime import date

class Services:
    def __init__(self, students_repo, assignments_repo, grades_repo) -> None:
        self.__students_repo = students_repo
        self.__assignments_repo = assignments_repo
        self.__grades_repo = grades_repo
    
    def add_student(self, student_id, name, group) -> None:
        """
        Adds a student to the repository
        :param student_id: the id of the student
        :param name: the name of the student
        :param group: the group of the student
        :return: None
        """
        student = Student(student_id, name, group)
        self.__students_repo.add_student(student)
    
    def remove_student(self, student_id) -> None:
        """
        Removes a student from the repository
        :param student_id: the id of the student
        :return: None
        """
        self.__students_repo.remove_student(student_id)
        list_of_grades = self.__grades_repo.list_grades()
        for grade in list_of_grades:
            if grade.get_student_id() == student_id:
                self.__grades_repo.remove_grade(grade.get_student_id(), grade.get_assignment_id())
    
    def update_student(self, student_id, name, group) -> None:
        """
        Updates a student from the repository
        :param student_id: the id of the student
        :param name: the name of the student
        :param group: the group of the student
        :return: None
        """
        student = Student(student_id, name, group)
        self.__students_repo.update_student(student)
    
    def list_students(self) -> list[Student]:
        """
        Returns a list of students
        :return: a list of students
        """
        return self.__students_repo.list_students()
    
    def add_assignment(self, assignment_id, description, deadline) -> None:
        """
        Adds an assignment to the repository
        :param assignment_id: the id of the assignment
        :param description: the description of the assignment
        :param deadline: the deadline of the assignment
        :return: None
        """
        assignment = Assignment(assignment_id, description, deadline)
        self.__assignments_repo.add_assignment(assignment)

    def remove_assignment(self, assignment_id) -> None:
        """
        Removes an assignment from the repository
        :param assignment_id: the id of the assignment
        :return: None
        """
        self.__assignments_repo.remove_assignment(assignment_id)
        list_of_grades = self.__grades_repo.list_grades()
        for grade in list_of_grades:
            if grade.get_assignment_id() == assignment_id:
                self.__grades_repo.remove_grade(grade.get_student_id(), grade.get_assignment_id())
    
    def update_assignment(self, assignment_id, description, deadline) -> None:
        """
        Updates an assignment from the repository
        :param assignment_id: the id of the assignment
        :param description: the description of the assignment
        :param deadline: the deadline of the assignment
        :return: None
        """
        assignment = Assignment(assignment_id, description, deadline)
        self.__assignments_repo.update_assignment(assignment)
    
    def list_assignments(self) -> list[Assignment]:
        """
        Returns a list of assignments
        :return: a list of assignments
        """
        return self.__assignments_repo.list_assignments()
    
    def give_assignment_to_student(self, student_id, assignment_id) -> None:
        """
        Gives an assignment to a student
        :param student_id: the id of the student
        :param assignment_id: the id of the assignment
        :return: None
        """
        if not self.__students_repo.is_a_student(student_id):
            raise StudentNotFoundError("Student not found")
        if not self.__assignments_repo.is_an_assignment(assignment_id):
            raise AssignmentNotFoundError("Assignment not found")
        
        grade = Grade(assignment_id,student_id, None)
        self.__grades_repo.add_grade(grade)
    
    def give_assignment_to_group(self, group, assignment_id) -> None:
        """
        Gives an assignment to all students in a group
        :param group: the group of students
        :param assignment_id: the id of the assignment
        :return: None
        """
        if not self.__assignments_repo.is_an_assignment(assignment_id):
            raise AssignmentNotFoundError("Assignment not found")
        students = self.__students_repo.list_students()
        for student in students:
            if student.get_group() == group:
                grade = Grade(assignment_id,student.get_student_id(), None)
                try:
                    self.__grades_repo.add_grade(grade)
                except GradeAlreadyExistsError:
                    pass
        
    def list_grades(self) -> list[Grade]:
        return self.__grades_repo.list_grades()
    
    def list_grades_for_student(self, student_id) -> list[Grade]:
        grades = self.__grades_repo.list_grades()
        grades_for_student = []
        for grade in grades:
            if grade.get_student_id() == student_id:
                grades_for_student.append(grade)
        return grades_for_student
    
    def list_ungraded_assignments_for_student(self, student_id) -> list[Grade]:
        if not self.__students_repo.is_a_student(student_id):
            raise StudentNotFoundError("Student not found")
        grades = self.__grades_repo.list_grades()
        ungraded_assignments_for_student = []
        ungraded_assignments_id_for_student=[]
        for grade in grades:
            if grade.get_student_id() == student_id and grade.get_grade_value() == None:
                ungraded_assignments_id_for_student.append(grade.get_assignment_id())
        assignments = self.__assignments_repo.list_assignments()
        for assignment in assignments:
            if assignment.get_assignment_id() in ungraded_assignments_id_for_student:
                ungraded_assignments_for_student.append(assignment)
        return ungraded_assignments_for_student
    
    def add_grade(self, student_id, assignment_id, grade) -> None:
        if not self.__students_repo.is_a_student(student_id):
            raise StudentNotFoundError("Student not found")
        if not self.__assignments_repo.is_an_assignment(assignment_id):
            raise AssignmentNotFoundError("Assignment not found")
        grade = Grade(assignment_id,student_id, grade)
        self.__grades_repo.update_grade(grade)
        
    def list_students_with_assignment(self,assignment_id) -> list[Student]:
        students = self.__students_repo.list_students()
        students_with_assignment = []
        grades=sorted(self.__grades_repo.list_grades(), key=lambda grade: grade.get_grade_value() if grade.get_grade_value()!=None else 0 , reverse=True)
        for grade in grades:
            if grade.get_assignment_id() == assignment_id:
                for student in students:
                    if student.get_student_id() == grade.get_student_id():
                        students_with_assignment.append(student)
        return students_with_assignment
    
    def list_students_late_on_assignment(self,assignment_id) -> list[Student]:
        students = self.__students_repo.list_students()
        students_late_on_assignment = []
        grades= self.__grades_repo.list_grades()
        for grade in grades:
            if grade.get_assignment_id() == assignment_id and grade.get_grade_value() == None and self.__assignments_repo.get_assignment(assignment_id).get_deadline() < date.today():
                for student in students:
                    if student.get_student_id() == grade.get_student_id() :
                        students_late_on_assignment.append(student)
        return students_late_on_assignment

    def list_students_best_school_situation(self) -> list[Student]:
        students = self.__students_repo.list_students()
        students_best_school_situation = []
        grades=self.__grades_repo.list_grades()
        for student in students:
            sum_of_grades = 0
            number_of_grades = 0
            for grade in grades:
                if grade.get_student_id() == student.get_student_id() and grade.get_grade_value() != None:
                    sum_of_grades += grade.get_grade_value()
                    number_of_grades += 1
            if number_of_grades != 0:
                average = sum_of_grades / number_of_grades
                students_best_school_situation.append((student, average))
        students_best_school_situation = [student for student, average in sorted(students_best_school_situation, key=lambda student: student[1], reverse=True)]
        return students_best_school_situation