package services

import (
	"context"
	"fmt"
	"time"

	"github.com/breakpilot/consent-service/internal/database"
	"github.com/breakpilot/consent-service/internal/models"
	"github.com/breakpilot/consent-service/internal/services/matrix"

	"github.com/google/uuid"
)

// GradeService handles grade management and notifications
type GradeService struct {
	db     *database.DB
	matrix *matrix.MatrixService
}

// NewGradeService creates a new grade service
func NewGradeService(db *database.DB, matrixService *matrix.MatrixService) *GradeService {
	return &GradeService{
		db:     db,
		matrix: matrixService,
	}
}

// ========================================
// Grade CRUD
// ========================================

// CreateGrade creates a new grade for a student
func (s *GradeService) CreateGrade(ctx context.Context, req models.CreateGradeRequest, teacherID uuid.UUID) (*models.Grade, error) {
	studentID, err := uuid.Parse(req.StudentID)
	if err != nil {
		return nil, fmt.Errorf("invalid student ID: %w", err)
	}

	subjectID, err := uuid.Parse(req.SubjectID)
	if err != nil {
		return nil, fmt.Errorf("invalid subject ID: %w", err)
	}

	schoolYearID, err := uuid.Parse(req.SchoolYearID)
	if err != nil {
		return nil, fmt.Errorf("invalid school year ID: %w", err)
	}

	date, err := time.Parse("2006-01-02", req.Date)
	if err != nil {
		return nil, fmt.Errorf("invalid date format: %w", err)
	}

	// Get default grade scale for the school
	var gradeScaleID uuid.UUID
	var schoolID uuid.UUID
	err = s.db.Pool.QueryRow(ctx, `
		SELECT gs.id, gs.school_id
		FROM grade_scales gs
		JOIN students st ON st.school_id = gs.school_id
		WHERE st.id = $1 AND gs.is_default = true`, studentID).Scan(&gradeScaleID, &schoolID)
	if err != nil {
		return nil, fmt.Errorf("failed to get grade scale: %w", err)
	}

	weight := req.Weight
	if weight == 0 {
		weight = 1.0
	}

	grade := &models.Grade{
		ID:           uuid.New(),
		StudentID:    studentID,
		SubjectID:    subjectID,
		TeacherID:    teacherID,
		SchoolYearID: schoolYearID,
		GradeScaleID: gradeScaleID,
		Type:         req.Type,
		Value:        req.Value,
		Weight:       weight,
		Date:         date,
		Title:        req.Title,
		Description:  req.Description,
		IsVisible:    true,
		Semester:     req.Semester,
		CreatedAt:    time.Now(),
		UpdatedAt:    time.Now(),
	}

	query := `
		INSERT INTO grades (id, student_id, subject_id, teacher_id, school_year_id, grade_scale_id, type, value, weight, date, title, description, is_visible, semester, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
		RETURNING id`

	err = s.db.Pool.QueryRow(ctx, query,
		grade.ID, grade.StudentID, grade.SubjectID, grade.TeacherID,
		grade.SchoolYearID, grade.GradeScaleID, grade.Type, grade.Value,
		grade.Weight, grade.Date, grade.Title, grade.Description,
		grade.IsVisible, grade.Semester, grade.CreatedAt, grade.UpdatedAt,
	).Scan(&grade.ID)

	if err != nil {
		return nil, fmt.Errorf("failed to create grade: %w", err)
	}

	// Send notification to parents if grade is visible
	if grade.IsVisible {
		go s.notifyParentsOfNewGrade(context.Background(), grade)
	}

	return grade, nil
}

// GetGrade retrieves a grade by ID
func (s *GradeService) GetGrade(ctx context.Context, gradeID uuid.UUID) (*models.Grade, error) {
	query := `
		SELECT id, student_id, subject_id, teacher_id, school_year_id, grade_scale_id, type, value, weight, date, title, description, is_visible, semester, created_at, updated_at
		FROM grades
		WHERE id = $1`

	grade := &models.Grade{}
	err := s.db.Pool.QueryRow(ctx, query, gradeID).Scan(
		&grade.ID, &grade.StudentID, &grade.SubjectID, &grade.TeacherID,
		&grade.SchoolYearID, &grade.GradeScaleID, &grade.Type, &grade.Value,
		&grade.Weight, &grade.Date, &grade.Title, &grade.Description,
		&grade.IsVisible, &grade.Semester, &grade.CreatedAt, &grade.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to get grade: %w", err)
	}

	return grade, nil
}

// UpdateGrade updates an existing grade
func (s *GradeService) UpdateGrade(ctx context.Context, gradeID uuid.UUID, value float64, title, description *string) error {
	query := `
		UPDATE grades
		SET value = $1, title = COALESCE($2, title), description = COALESCE($3, description), updated_at = NOW()
		WHERE id = $4`

	result, err := s.db.Pool.Exec(ctx, query, value, title, description, gradeID)
	if err != nil {
		return fmt.Errorf("failed to update grade: %w", err)
	}

	if result.RowsAffected() == 0 {
		return fmt.Errorf("grade not found")
	}

	return nil
}

// DeleteGrade deletes a grade
func (s *GradeService) DeleteGrade(ctx context.Context, gradeID uuid.UUID) error {
	result, err := s.db.Pool.Exec(ctx, `DELETE FROM grades WHERE id = $1`, gradeID)
	if err != nil {
		return fmt.Errorf("failed to delete grade: %w", err)
	}

	if result.RowsAffected() == 0 {
		return fmt.Errorf("grade not found")
	}

	return nil
}

// ========================================
// Grade Queries
// ========================================

// GetStudentGrades gets all grades for a student in a school year
func (s *GradeService) GetStudentGrades(ctx context.Context, studentID, schoolYearID uuid.UUID) ([]models.Grade, error) {
	query := `
		SELECT id, student_id, subject_id, teacher_id, school_year_id, grade_scale_id, type, value, weight, date, title, description, is_visible, semester, created_at, updated_at
		FROM grades
		WHERE student_id = $1 AND school_year_id = $2 AND is_visible = true
		ORDER BY date DESC`

	rows, err := s.db.Pool.Query(ctx, query, studentID, schoolYearID)
	if err != nil {
		return nil, fmt.Errorf("failed to get student grades: %w", err)
	}
	defer rows.Close()

	var grades []models.Grade
	for rows.Next() {
		var grade models.Grade
		err := rows.Scan(
			&grade.ID, &grade.StudentID, &grade.SubjectID, &grade.TeacherID,
			&grade.SchoolYearID, &grade.GradeScaleID, &grade.Type, &grade.Value,
			&grade.Weight, &grade.Date, &grade.Title, &grade.Description,
			&grade.IsVisible, &grade.Semester, &grade.CreatedAt, &grade.UpdatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan grade: %w", err)
		}
		grades = append(grades, grade)
	}

	return grades, nil
}

// GetStudentGradesBySubject gets grades for a student in a specific subject
func (s *GradeService) GetStudentGradesBySubject(ctx context.Context, studentID, subjectID, schoolYearID uuid.UUID, semester int) ([]models.Grade, error) {
	query := `
		SELECT id, student_id, subject_id, teacher_id, school_year_id, grade_scale_id, type, value, weight, date, title, description, is_visible, semester, created_at, updated_at
		FROM grades
		WHERE student_id = $1 AND subject_id = $2 AND school_year_id = $3 AND semester = $4 AND is_visible = true
		ORDER BY date DESC`

	rows, err := s.db.Pool.Query(ctx, query, studentID, subjectID, schoolYearID, semester)
	if err != nil {
		return nil, fmt.Errorf("failed to get grades by subject: %w", err)
	}
	defer rows.Close()

	var grades []models.Grade
	for rows.Next() {
		var grade models.Grade
		err := rows.Scan(
			&grade.ID, &grade.StudentID, &grade.SubjectID, &grade.TeacherID,
			&grade.SchoolYearID, &grade.GradeScaleID, &grade.Type, &grade.Value,
			&grade.Weight, &grade.Date, &grade.Title, &grade.Description,
			&grade.IsVisible, &grade.Semester, &grade.CreatedAt, &grade.UpdatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan grade: %w", err)
		}
		grades = append(grades, grade)
	}

	return grades, nil
}

// GetClassGradesBySubject gets all grades for a class in a subject (Notenspiegel)
func (s *GradeService) GetClassGradesBySubject(ctx context.Context, classID, subjectID, schoolYearID uuid.UUID, semester int) ([]models.StudentGradeOverview, error) {
	// Get all students in the class
	studentsQuery := `
		SELECT id, first_name, last_name
		FROM students
		WHERE class_id = $1 AND is_active = true
		ORDER BY last_name, first_name`

	rows, err := s.db.Pool.Query(ctx, studentsQuery, classID)
	if err != nil {
		return nil, fmt.Errorf("failed to get students: %w", err)
	}
	defer rows.Close()

	var students []struct {
		ID        uuid.UUID
		FirstName string
		LastName  string
	}

	for rows.Next() {
		var student struct {
			ID        uuid.UUID
			FirstName string
			LastName  string
		}
		if err := rows.Scan(&student.ID, &student.FirstName, &student.LastName); err != nil {
			return nil, fmt.Errorf("failed to scan student: %w", err)
		}
		students = append(students, student)
	}

	// Get subject info
	var subject models.Subject
	err = s.db.Pool.QueryRow(ctx, `SELECT id, school_id, name, short_name, color, is_active, created_at FROM subjects WHERE id = $1`, subjectID).Scan(
		&subject.ID, &subject.SchoolID, &subject.Name, &subject.ShortName, &subject.Color, &subject.IsActive, &subject.CreatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to get subject: %w", err)
	}

	var overviews []models.StudentGradeOverview

	for _, student := range students {
		grades, err := s.GetStudentGradesBySubject(ctx, student.ID, subjectID, schoolYearID, semester)
		if err != nil {
			continue
		}

		// Calculate averages
		var totalWeight, weightedSum float64
		var oralWeight, oralSum float64
		var examWeight, examSum float64

		for _, grade := range grades {
			totalWeight += grade.Weight
			weightedSum += grade.Value * grade.Weight

			if grade.Type == models.GradeTypeOral || grade.Type == models.GradeTypeParticipation {
				oralWeight += grade.Weight
				oralSum += grade.Value * grade.Weight
			} else if grade.Type == models.GradeTypeExam || grade.Type == models.GradeTypeTest {
				examWeight += grade.Weight
				examSum += grade.Value * grade.Weight
			}
		}

		var average, oralAverage, examAverage float64
		if totalWeight > 0 {
			average = weightedSum / totalWeight
		}
		if oralWeight > 0 {
			oralAverage = oralSum / oralWeight
		}
		if examWeight > 0 {
			examAverage = examSum / examWeight
		}

		overview := models.StudentGradeOverview{
			Student: models.Student{
				ID:        student.ID,
				FirstName: student.FirstName,
				LastName:  student.LastName,
			},
			Subject:     subject,
			Grades:      grades,
			Average:     average,
			OralAverage: oralAverage,
			ExamAverage: examAverage,
			Semester:    semester,
		}

		overviews = append(overviews, overview)
	}

	return overviews, nil
}

