import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateGradeDto, UpdateGradeDto } from './dto';

@Injectable()
export class GradesService {
  constructor(private prisma: PrismaService) {}

  // Lehrer: Note erstellen
  async createGrade(createGradeDto: CreateGradeDto) {
    return this.prisma.grade.create({
      data: {
        ...createGradeDto,
        date: new Date(createGradeDto.date),
      },
      include: {
        student: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        timetableEntry: {
          include: {
            subject: true,
            teacher: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
            room: true,
          },
        },
      },
    });
  }

  // Lehrer: Alle Noten für einen bestimmten Stundenplan-Eintrag und Woche abrufen
  async getGradesByTimetableEntry(timetableEntryId: number, weekNumber: number, year: number) {
    return this.prisma.grade.findMany({
      where: {
        timetableEntryId,
        weekNumber,
        year,
      },
      include: {
        student: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
      },
      orderBy: [
        { student: { username: 'asc' } },
      ],
    });
  }

  // Schüler: Eigene Noten für einen bestimmten Stundenplan-Eintrag und Woche abrufen
  async getStudentGradesByTimetableEntry(studentId: number, timetableEntryId: number, weekNumber: number, year: number) {
    return this.prisma.grade.findMany({
      where: {
        studentId,
        timetableEntryId,
        weekNumber,
        year,
      },
      include: {
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        timetableEntry: {
          include: {
            subject: true,
            teacher: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
            room: true,
          },
        },
      },
      orderBy: {
        date: 'desc',
      },
    });
  }

  // Schüler: Alle eigenen Noten abrufen
  async getStudentGrades(studentId: number) {
    return this.prisma.grade.findMany({
      where: { studentId },
      include: {
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        timetableEntry: {
          include: {
            subject: true,
            teacher: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
            room: true,
          },
        },
      },
      orderBy: {
        date: 'desc',
      },
    });
  }

  // Lehrer: Alle Noten abrufen (optional mit Filter)
  async getAllGrades(filters?: { studentId?: number; timetableEntryId?: number; teacherId?: number }) {
    return this.prisma.grade.findMany({
      where: filters,
      include: {
        student: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        timetableEntry: {
          include: {
            subject: true,
            teacher: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
            room: true,
          },
        },
      },
      orderBy: {
        date: 'desc',
      },
    });
  }

  // Lehrer: Note aktualisieren
  async updateGrade(id: number, updateGradeDto: UpdateGradeDto) {
    const grade = await this.prisma.grade.findUnique({ where: { id } });

    if (!grade) {
      throw new NotFoundException(`Note mit ID ${id} nicht gefunden`);
    }

    return this.prisma.grade.update({
      where: { id },
      data: {
        ...updateGradeDto,
        date: updateGradeDto.date ? new Date(updateGradeDto.date) : undefined,
      },
      include: {
        student: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        timetableEntry: {
          include: {
            subject: true,
            teacher: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
            room: true,
          },
        },
      },
    });
  }

  // Lehrer: Note löschen
  async deleteGrade(id: number) {
    const grade = await this.prisma.grade.findUnique({ where: { id } });

    if (!grade) {
      throw new NotFoundException(`Note mit ID ${id} nicht gefunden`);
    }

    return this.prisma.grade.delete({ where: { id } });
  }

  // Schüler: Durchschnittsnote für ein Fach berechnen
  async calculateAverageForSubject(studentId: number, timetableEntryId: number) {
    const grades = await this.prisma.grade.findMany({
      where: {
        studentId,
        timetableEntryId,
      },
    });

    if (grades.length === 0) {
      return null;
    }

    // Gewichtete Durchschnittsnote berechnen
    const totalWeight = grades.reduce((sum, g) => sum + (g.weight || 1.0), 0);
    const weightedSum = grades.reduce((sum, g) => sum + g.grade * (g.weight || 1.0), 0);

    return {
      average: Number((weightedSum / totalWeight).toFixed(2)),
      gradeCount: grades.length,
      grades: grades.map(g => ({
        id: g.id,
        grade: g.grade,
        gradeType: g.gradeType,
        weight: g.weight,
        title: g.title,
        date: g.date,
      })),
    };
  }

  // Schüler: Gesamtübersicht über alle Fächer
  async getStudentOverview(studentId: number) {
    const grades = await this.prisma.grade.findMany({
      where: { studentId },
      include: {
        timetableEntry: {
          include: {
            subject: true,
            teacher: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
            room: true,
          },
        },
      },
    });

    // Gruppiere Noten nach Fach
    const gradesBySubject = grades.reduce((acc, grade) => {
      const key = `${grade.timetableEntryId}`;
      if (!acc[key]) {
        acc[key] = {
          timetableEntryId: grade.timetableEntryId,
          subject: grade.timetableEntry.subject,
          teacher: grade.timetableEntry.teacher,
          grades: [],
        };
      }
      acc[key].grades.push(grade);
      return acc;
    }, {});

    // Berechne Durchschnitt für jedes Fach
    const overview = Object.values(gradesBySubject).map((subjectData: any) => {
      const totalWeight = subjectData.grades.reduce((sum, g) => sum + (g.weight || 1.0), 0);
      const weightedSum = subjectData.grades.reduce((sum, g) => sum + g.grade * (g.weight || 1.0), 0);

      return {
        timetableEntryId: subjectData.timetableEntryId,
        subject: subjectData.subject,
        teacher: subjectData.teacher,
        average: Number((weightedSum / totalWeight).toFixed(2)),
        gradeCount: subjectData.grades.length,
        latestGrade: subjectData.grades.sort((a, b) =>
          new Date(b.date).getTime() - new Date(a.date).getTime()
        )[0],
      };
    });

    return overview;
  }

  // Lehrer: Komplette Notenübersicht für alle Fächer
  async getTeacherGradesOverview() {
    // Alle Stundenplan-Einträge mit Noten abrufen
    const timetableEntries = await this.prisma.timetableEntry.findMany({
      include: {
        subject: true,
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        room: true,
        grades: {
          include: {
            student: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
          },
          orderBy: [
            { student: { username: 'asc' } },
            { date: 'desc' },
          ],
        },
      },
      orderBy: {
        subject: { name: 'asc' },
      },
    });

    // Gruppiere nach Fachname (subject.name) statt nach timetableEntryId
    const subjectGroups = timetableEntries.reduce((acc, entry) => {
      const subjectName = entry.subject.name;

      if (!acc[subjectName]) {
        acc[subjectName] = {
          subject: entry.subject,
          timetableEntryIds: [],
          entries: [],
          allGrades: [],
        };
      }

      // Sammle alle timetableEntryIds für dieses Fach
      acc[subjectName].timetableEntryIds.push(entry.id);
      acc[subjectName].entries.push({
        id: entry.id,
        teacher: entry.teacher,
        dayOfWeek: entry.dayOfWeek,
        startTime: entry.startTime,
        endTime: entry.endTime,
      });

      // Sammle alle Noten für dieses Fach
      acc[subjectName].allGrades.push(...entry.grades);

      return acc;
    }, {});

    // Berechne Statistiken für jedes gruppierte Fach
    return Object.values(subjectGroups).map((subjectData: any) => {
      // Gruppiere alle Noten nach Schüler
      const gradesByStudent = subjectData.allGrades.reduce((acc, grade) => {
        if (!acc[grade.studentId]) {
          acc[grade.studentId] = {
            student: grade.student,
            grades: [],
          };
        }
        acc[grade.studentId].grades.push(grade);
        return acc;
      }, {});

      // Berechne Durchschnitt pro Schüler
      const studentAverages = Object.values(gradesByStudent).map((data: any) => {
        const totalWeight = data.grades.reduce((sum, g) => sum + (g.weight || 1.0), 0);
        const weightedSum = data.grades.reduce((sum, g) => sum + g.grade * (g.weight || 1.0), 0);
        const average = totalWeight > 0 ? Number((weightedSum / totalWeight).toFixed(2)) : null;

        return {
          student: data.student,
          average,
          gradeCount: data.grades.length,
          grades: data.grades,
        };
      });

      return {
        subject: subjectData.subject,
        timetableEntryIds: subjectData.timetableEntryIds, // Alle IDs für dieses Fach
        timetableEntries: subjectData.entries, // Alle Stundenplan-Einträge für dieses Fach
        studentCount: studentAverages.length,
        totalGrades: subjectData.allGrades.length,
        students: studentAverages,
      };
    }).filter(entry => entry.totalGrades > 0); // Nur Fächer mit Noten anzeigen
  }

  // Lehrer: Detaillierte Notenansicht für ein spezifisches Fach (einzelner Stundenplan-Eintrag)
  async getSubjectGradeDetails(timetableEntryId: number) {
    const timetableEntry = await this.prisma.timetableEntry.findUnique({
      where: { id: timetableEntryId },
      include: {
        subject: true,
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        room: true,
        grades: {
          include: {
            student: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
          },
          orderBy: [
            { student: { username: 'asc' } },
            { date: 'desc' },
          ],
        },
      },
    });

    if (!timetableEntry) {
      throw new NotFoundException(`Stundenplan-Eintrag mit ID ${timetableEntryId} nicht gefunden`);
    }

    // Gruppiere Noten nach Schüler
    const gradesByStudent = timetableEntry.grades.reduce((acc, grade) => {
      if (!acc[grade.studentId]) {
        acc[grade.studentId] = {
          student: grade.student,
          grades: [],
        };
      }
      acc[grade.studentId].grades.push(grade);
      return acc;
    }, {});

    // Berechne Durchschnitt und Details pro Schüler
    const students = Object.values(gradesByStudent).map((data: any) => {
      const totalWeight = data.grades.reduce((sum, g) => sum + (g.weight || 1.0), 0);
      const weightedSum = data.grades.reduce((sum, g) => sum + g.grade * (g.weight || 1.0), 0);
      const average = totalWeight > 0 ? Number((weightedSum / totalWeight).toFixed(2)) : null;

      return {
        student: data.student,
        average,
        totalWeight,
        gradeCount: data.grades.length,
        grades: data.grades.map(g => ({
          id: g.id,
          grade: g.grade,
          gradeType: g.gradeType,
          weight: g.weight || 1.0,
          title: g.title,
          description: g.description,
          maxPoints: g.maxPoints,
          achievedPoints: g.achievedPoints,
          date: g.date,
          weekNumber: g.weekNumber,
          year: g.year,
        })),
      };
    });

    return {
      timetableEntry: {
        id: timetableEntry.id,
        subject: timetableEntry.subject,
        teacher: timetableEntry.teacher,
        room: timetableEntry.room,
        dayOfWeek: timetableEntry.dayOfWeek,
        startTime: timetableEntry.startTime,
        endTime: timetableEntry.endTime,
      },
      students,
      totalStudents: students.length,
      totalGrades: timetableEntry.grades.length,
    };
  }

  // Lehrer: Detaillierte Notenansicht für alle Stundenplan-Einträge mit gleichem Fachnamen
  async getSubjectGradeDetailsByName(subjectName: string) {
    // Alle Stundenplan-Einträge mit diesem Fachnamen finden
    const timetableEntries = await this.prisma.timetableEntry.findMany({
      where: {
        subject: { name: subjectName }
      },
      include: {
        subject: true,
        teacher: {
          select: {
            id: true,
            username: true,
            email: true,
          },
        },
        room: true,
        grades: {
          include: {
            student: {
              select: {
                id: true,
                username: true,
                email: true,
              },
            },
          },
          orderBy: [
            { student: { username: 'asc' } },
            { date: 'desc' },
          ],
        },
      },
      orderBy: [
        { dayOfWeek: 'asc' },
        { startTime: 'asc' },
      ],
    });

    if (timetableEntries.length === 0) {
      throw new NotFoundException(`Keine Stundenplan-Einträge für Fach "${subjectName}" gefunden`);
    }

    // Alle Noten über alle Stundenplan-Einträge sammeln
    const allGrades = timetableEntries.flatMap(entry => entry.grades);

    // Gruppiere Noten nach Schüler
    const gradesByStudent = allGrades.reduce((acc, grade) => {
      if (!acc[grade.studentId]) {
        acc[grade.studentId] = {
          student: grade.student,
          grades: [],
        };
      }
      acc[grade.studentId].grades.push(grade);
      return acc;
    }, {});

    // Berechne Durchschnitt und Details pro Schüler
    const students = Object.values(gradesByStudent).map((data: any) => {
      const totalWeight = data.grades.reduce((sum, g) => sum + (g.weight || 1.0), 0);
      const weightedSum = data.grades.reduce((sum, g) => sum + g.grade * (g.weight || 1.0), 0);
      const average = totalWeight > 0 ? Number((weightedSum / totalWeight).toFixed(2)) : null;

      return {
        student: data.student,
        average,
        totalWeight,
        gradeCount: data.grades.length,
        grades: data.grades.map(g => ({
          id: g.id,
          grade: g.grade,
          gradeType: g.gradeType,
          weight: g.weight || 1.0,
          title: g.title,
          description: g.description,
          maxPoints: g.maxPoints,
          achievedPoints: g.achievedPoints,
          date: g.date,
          weekNumber: g.weekNumber,
          year: g.year,
        })),
      };
    });

    return {
      subject: timetableEntries[0].subject,
      timetableEntries: timetableEntries.map(entry => ({
        id: entry.id,
        teacher: entry.teacher,
        room: entry.room,
        dayOfWeek: entry.dayOfWeek,
        startTime: entry.startTime,
        endTime: entry.endTime,
      })),
      students,
      totalStudents: students.length,
      totalGrades: allGrades.length,
    };
  }
}
