package seed

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
)

// StudentResult holds created student info
type StudentResult struct {
	ID        uuid.UUID
	FirstName string
	LastName  string
}

func (s *Seeder) seedStudentsForClass(ctx context.Context, classID uuid.UUID) ([]StudentResult, error) {
	// 15-25 students per class
	numStudents := 15 + s.rng.Intn(11) // 15-25

	var students []StudentResult
	usedNames := make(map[string]bool)

	for i := 0; i < numStudents; i++ {
		// Generate unique name combination
		var firstName, lastName string
		for {
			firstName = firstNames[s.rng.Intn(len(firstNames))]
			lastName = lastNames[s.rng.Intn(len(lastNames))]
			key := firstName + lastName
			if !usedNames[key] {
				usedNames[key] = true
				break
			}
		}

		id := uuid.New()
		// Generate birth date (10-13 years old)
		birthYear := time.Now().Year() - 10 - s.rng.Intn(4)
		birthMonth := time.Month(1 + s.rng.Intn(12))
		birthDay := 1 + s.rng.Intn(28)
		birthDate := time.Date(birthYear, birthMonth, birthDay, 0, 0, 0, 0, time.UTC)

		studentNumber := fmt.Sprintf("S%d%04d", time.Now().Year()%100, i+1)

		_, err := s.pool.Exec(ctx, `
			INSERT INTO students (id, class_id, first_name, last_name, birth_date, student_number)
			VALUES ($1, $2, $3, $4, $5, $6)
			ON CONFLICT DO NOTHING
		`, id, classID, firstName, lastName, birthDate, studentNumber)

		if err != nil {
			return nil, err
		}

		students = append(students, StudentResult{
			ID:        id,
			FirstName: firstName,
			LastName:  lastName,
		})
	}

	return students, nil
}

func (s *Seeder) seedExamsAndResults(
	ctx context.Context,
	classID uuid.UUID,
	subjectID uuid.UUID,
	students []StudentResult,
	schoolYearID uuid.UUID,
	subj SubjectConfig,
) error {
	if subj.ExamsPerYear == 0 {
		return nil // No exams for this subject (e.g., Sport)
	}

	examDates := []time.Time{
		time.Date(2024, 10, 15, 0, 0, 0, 0, time.UTC),
		time.Date(2024, 12, 10, 0, 0, 0, 0, time.UTC),
		time.Date(2025, 2, 20, 0, 0, 0, 0, time.UTC),
		time.Date(2025, 5, 15, 0, 0, 0, 0, time.UTC),
	}

	for i := 0; i < subj.ExamsPerYear; i++ {
		examID := uuid.New()
		examDate := examDates[i%len(examDates)]
		maxPoints := 50.0 + float64(s.rng.Intn(51)) // 50-100 points

		title := fmt.Sprintf("%s Klassenarbeit %d", subj.Name, i+1)

		_, err := s.pool.Exec(ctx, `
			INSERT INTO exams (id, teacher_id, class_id, subject_id, title, exam_type, max_points, status, exam_date, difficulty_level, duration_minutes)
			VALUES ($1, $2, $3, $4, $5, 'klassenarbeit', $6, 'archived', $7, $8, $9)
			ON CONFLICT DO NOTHING
		`, examID, s.teacherID, classID, subjectID, title, maxPoints, examDate, 3, 45)

		if err != nil {
			return err
		}

		// Create results for each student
		for _, student := range students {
			// Generate realistic grade distribution (mostly 2-4)
			grade := s.generateRealisticGrade()
			percentage := s.gradeToPercentage(grade)
			points := (percentage / 100.0) * maxPoints
			isAbsent := s.rng.Float64() < 0.03 // 3% absence rate

			if isAbsent {
				_, err = s.pool.Exec(ctx, `
					INSERT INTO exam_results (exam_id, student_id, is_absent, approved_by_teacher)
					VALUES ($1, $2, true, true)
					ON CONFLICT (exam_id, student_id) DO NOTHING
				`, examID, student.ID)
			} else {
				_, err = s.pool.Exec(ctx, `
					INSERT INTO exam_results (exam_id, student_id, points_achieved, grade, percentage, approved_by_teacher)
					VALUES ($1, $2, $3, $4, $5, true)
					ON CONFLICT (exam_id, student_id) DO NOTHING
				`, examID, student.ID, points, grade, percentage)
			}

			if err != nil {
				return err
			}
		}
	}

	// Create grade overview entries for each student
	for _, student := range students {
		err := s.createGradeOverview(ctx, student.ID, subjectID, schoolYearID, subj)
		if err != nil {
			return err
		}
	}

	return nil
}

func (s *Seeder) generateRealisticGrade() float64 {
	// German grading: 1 (best) to 6 (worst)
	// Normal distribution centered around 3.0
	grades := []float64{1.0, 1.3, 1.7, 2.0, 2.3, 2.7, 3.0, 3.3, 3.7, 4.0, 4.3, 4.7, 5.0, 5.3, 5.7, 6.0}
	weights := []int{2, 4, 6, 10, 12, 14, 16, 14, 12, 8, 5, 3, 2, 1, 1, 1} // Weighted distribution

	total := 0
	for _, w := range weights {
		total += w
	}

	r := s.rng.Intn(total)
	cumulative := 0
	for i, w := range weights {
		cumulative += w
		if r < cumulative {
			return grades[i]
		}
	}
	return 3.0
}

func (s *Seeder) gradeToPercentage(grade float64) float64 {
	// Convert German grade to percentage
	// 1.0 = 100%, 6.0 = 0%
	return 100.0 - ((grade - 1.0) * 20.0)
}

func (s *Seeder) createGradeOverview(
	ctx context.Context,
	studentID uuid.UUID,
	subjectID uuid.UUID,
	schoolYearID uuid.UUID,
	subj SubjectConfig,
) error {
	// Create for both semesters
	for semester := 1; semester <= 2; semester++ {
		writtenAvg := s.generateRealisticGrade()
		oralGrade := s.generateRealisticGrade()

		// Calculate final grade based on weights
		finalGrade := (writtenAvg*float64(subj.WrittenWeight) + oralGrade*float64(100-subj.WrittenWeight)) / 100.0
		// Round to nearest 0.5
		finalGrade = float64(int(finalGrade*2+0.5)) / 2.0

		_, err := s.pool.Exec(ctx, `
			INSERT INTO grade_overview (student_id, subject_id, school_year_id, semester, written_grade_avg, written_grade_count, oral_grade, final_grade, written_weight, oral_weight)
			VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
			ON CONFLICT (student_id, subject_id, school_year_id, semester) DO UPDATE SET
				written_grade_avg = EXCLUDED.written_grade_avg,
				oral_grade = EXCLUDED.oral_grade,
				final_grade = EXCLUDED.final_grade
		`, studentID, subjectID, schoolYearID, semester, writtenAvg, subj.ExamsPerYear/2+1, oralGrade, finalGrade, subj.WrittenWeight, 100-subj.WrittenWeight)

		if err != nil {
			return err
		}
	}

	return nil
}

func (s *Seeder) seedAttendance(ctx context.Context, students []StudentResult, sy SchoolYearResult) error {
	for _, student := range students {
		// 0-10 absence days per student
		absenceDays := s.rng.Intn(11)

		for i := 0; i < absenceDays; i++ {
			// Random date within school year
			dayOffset := s.rng.Intn(int(sy.EndDate.Sub(sy.StartDate).Hours() / 24))
			absenceDate := sy.StartDate.AddDate(0, 0, dayOffset)

			// Skip weekends
			if absenceDate.Weekday() == time.Saturday || absenceDate.Weekday() == time.Sunday {
				continue
			}

			// 80% excused, 20% unexcused
			status := "absent_excused"
			if s.rng.Float64() < 0.2 {
				status = "absent_unexcused"
			}

			reasons := []string{
				"Krankheit",
				"Arzttermin",
				"Familiaere Gruende",
				"",
			}
			reason := reasons[s.rng.Intn(len(reasons))]

			_, err := s.pool.Exec(ctx, `
				INSERT INTO attendance (student_id, date, status, periods, reason)
				VALUES ($1, $2, $3, $4, $5)
				ON CONFLICT (student_id, date) DO NOTHING
			`, student.ID, absenceDate, status, s.rng.Intn(6)+1, reason)

			if err != nil {
				return err
			}
		}
	}

	return nil
}

// Statistics holds calculated statistics for a class
type Statistics struct {
	ClassID           uuid.UUID          `json:"class_id"`
	ClassName         string             `json:"class_name"`
	StudentCount      int                `json:"student_count"`
	ClassAverage      float64            `json:"class_average"`
	GradeDistribution map[string]int     `json:"grade_distribution"`
	BestGrade         float64            `json:"best_grade"`
	WorstGrade        float64            `json:"worst_grade"`
	PassRate          float64            `json:"pass_rate"`
	StudentsAtRisk    int                `json:"students_at_risk"`
	SubjectAverages   map[string]float64 `json:"subject_averages"`
}

// CalculateClassStatistics calculates statistics for a class
func (s *Seeder) CalculateClassStatistics(ctx context.Context, classID uuid.UUID) (*Statistics, error) {
	stats := &Statistics{
		ClassID:           classID,
		GradeDistribution: make(map[string]int),
		SubjectAverages:   make(map[string]float64),
	}

	// Initialize grade distribution
	for i := 1; i <= 6; i++ {
		stats.GradeDistribution[fmt.Sprintf("%d", i)] = 0
	}

	// Get class info and student count
	err := s.pool.QueryRow(ctx, `
		SELECT c.name, COUNT(s.id)
		FROM classes c
		LEFT JOIN students s ON s.class_id = c.id
		WHERE c.id = $1
		GROUP BY c.name
	`, classID).Scan(&stats.ClassName, &stats.StudentCount)
	if err != nil {
		return nil, err
	}

	// Get grade statistics from exam results
	rows, err := s.pool.Query(ctx, `
		SELECT
			COALESCE(AVG(er.grade), 0) as avg_grade,
			COALESCE(MIN(er.grade), 0) as best_grade,
			COALESCE(MAX(er.grade), 0) as worst_grade,
			COUNT(CASE WHEN er.grade <= 4.0 THEN 1 END) as passed,
			COUNT(er.id) as total,
			FLOOR(er.grade) as grade_bucket,
			COUNT(*) as bucket_count
		FROM exam_results er
		JOIN exams e ON e.id = er.exam_id
		JOIN students s ON s.id = er.student_id
		WHERE s.class_id = $1 AND er.grade IS NOT NULL
		GROUP BY FLOOR(er.grade)
	`, classID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var totalPassed, totalExams int
	for rows.Next() {
		var avgGrade, bestGrade, worstGrade float64
		var passed, total int
		var gradeBucket float64
		var bucketCount int

		err := rows.Scan(&avgGrade, &bestGrade, &worstGrade, &passed, &total, &gradeBucket, &bucketCount)
		if err != nil {
			continue
		}

		stats.ClassAverage = avgGrade
		stats.BestGrade = bestGrade
		stats.WorstGrade = worstGrade
		totalPassed += passed
		totalExams += total

		gradeKey := fmt.Sprintf("%d", int(gradeBucket))
		stats.GradeDistribution[gradeKey] = bucketCount
	}

	if totalExams > 0 {
		stats.PassRate = float64(totalPassed) / float64(totalExams) * 100
	}

	// Count students at risk (average >= 4.5)
	err = s.pool.QueryRow(ctx, `
		SELECT COUNT(DISTINCT s.id)
		FROM students s
		JOIN grade_overview go ON go.student_id = s.id
		WHERE s.class_id = $1 AND go.final_grade >= 4.5
	`, classID).Scan(&stats.StudentsAtRisk)
	if err != nil {
		stats.StudentsAtRisk = 0
	}

	// Get subject averages
	subjectRows, err := s.pool.Query(ctx, `
		SELECT sub.name, AVG(go.final_grade)
		FROM grade_overview go
		JOIN subjects sub ON sub.id = go.subject_id
		JOIN students s ON s.id = go.student_id
		WHERE s.class_id = $1
		GROUP BY sub.name
	`, classID)
	if err == nil {
		defer subjectRows.Close()
		for subjectRows.Next() {
			var name string
			var avg float64
			if err := subjectRows.Scan(&name, &avg); err == nil {
				stats.SubjectAverages[name] = avg
			}
		}
	}

	return stats, nil
}
