package seed

import (
	"context"
	"fmt"
	"log"
	"math/rand"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"
)

// Seeder generates demo data for the school service
type Seeder struct {
	pool      *pgxpool.Pool
	teacherID uuid.UUID
	rng       *rand.Rand
}

// NewSeeder creates a new seeder instance
func NewSeeder(pool *pgxpool.Pool, teacherID uuid.UUID) *Seeder {
	return &Seeder{
		pool:      pool,
		teacherID: teacherID,
		rng:       rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

// German first names for realistic demo data
var firstNames = []string{
	"Max", "Paul", "Leon", "Felix", "Jonas", "Lukas", "Tim", "Ben", "Finn", "Elias",
	"Emma", "Mia", "Hannah", "Sophia", "Lena", "Anna", "Marie", "Leonie", "Lara", "Laura",
	"Noah", "Luis", "David", "Moritz", "Jan", "Niklas", "Tom", "Simon", "Erik", "Jannik",
	"Lea", "Julia", "Lisa", "Sarah", "Clara", "Amelie", "Emily", "Maja", "Zoe", "Lina",
	"Alexander", "Maximilian", "Sebastian", "Philipp", "Julian", "Fabian", "Tobias", "Christian",
	"Katharina", "Christina", "Johanna", "Franziska", "Antonia", "Victoria", "Helena", "Charlotte",
}

// German last names for realistic demo data
var lastNames = []string{
	"Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker",
	"Schulz", "Hoffmann", "Koch", "Bauer", "Richter", "Klein", "Wolf", "Schroeder",
	"Neumann", "Schwarz", "Zimmermann", "Braun", "Krueger", "Hofmann", "Hartmann", "Lange",
	"Schmitt", "Werner", "Schmitz", "Krause", "Meier", "Lehmann", "Schmid", "Schulze",
	"Maier", "Koehler", "Herrmann", "Koenig", "Walter", "Mayer", "Huber", "Kaiser",
	"Peters", "Lang", "Scholz", "Moeller", "Gross", "Jung", "Friedrich", "Keller",
}

// Subjects with exam data
type SubjectConfig struct {
	Name          string
	ShortName     string
	IsMain        bool
	ExamsPerYear  int
	WrittenWeight int
}

var subjects = []SubjectConfig{
	{"Deutsch", "De", true, 4, 60},
	{"Mathematik", "Ma", true, 4, 60},
	{"Englisch", "En", true, 4, 60},
	{"Biologie", "Bio", false, 3, 50},
	{"Geschichte", "Ge", false, 2, 50},
	{"Physik", "Ph", false, 3, 50},
	{"Chemie", "Ch", false, 2, 50},
	{"Sport", "Sp", false, 0, 30},
	{"Kunst", "Ku", false, 1, 40},
	{"Musik", "Mu", false, 1, 40},
}

// SeedAll generates all demo data
func (s *Seeder) SeedAll(ctx context.Context) error {
	log.Println("Starting seed data generation...")

	// 1. Create school years
	schoolYears, err := s.seedSchoolYears(ctx)
	if err != nil {
		return fmt.Errorf("seeding school years: %w", err)
	}
	log.Printf("Created %d school years", len(schoolYears))

	// 2. Create subjects
	subjectIDs, err := s.seedSubjects(ctx)
	if err != nil {
		return fmt.Errorf("seeding subjects: %w", err)
	}
	log.Printf("Created %d subjects", len(subjectIDs))

	// 3. Create classes with students
	for _, sy := range schoolYears {
		classes, err := s.seedClassesForYear(ctx, sy)
		if err != nil {
			return fmt.Errorf("seeding classes for year %s: %w", sy.Name, err)
		}

		for _, class := range classes {
			// Create students
			students, err := s.seedStudentsForClass(ctx, class.ID)
			if err != nil {
				return fmt.Errorf("seeding students for class %s: %w", class.Name, err)
			}
			log.Printf("Created %d students for class %s", len(students), class.Name)

			// Create exams and results
			for _, subj := range subjects {
				subjectID := subjectIDs[subj.Name]
				err := s.seedExamsAndResults(ctx, class.ID, subjectID, students, sy.ID, subj)
				if err != nil {
					return fmt.Errorf("seeding exams for class %s, subject %s: %w", class.Name, subj.Name, err)
				}
			}

			// Create attendance records
			err = s.seedAttendance(ctx, students, sy)
			if err != nil {
				return fmt.Errorf("seeding attendance for class %s: %w", class.Name, err)
			}
		}
	}

	log.Println("Seed data generation completed successfully!")
	return nil
}

// SchoolYearResult holds created school year info
type SchoolYearResult struct {
	ID        uuid.UUID
	Name      string
	StartDate time.Time
	EndDate   time.Time
	IsCurrent bool
}

func (s *Seeder) seedSchoolYears(ctx context.Context) ([]SchoolYearResult, error) {
	years := []struct {
		name      string
		startYear int
		isCurrent bool
	}{
		{"2022/23", 2022, false},
		{"2023/24", 2023, false},
		{"2024/25", 2024, true},
	}

	var results []SchoolYearResult
	for _, y := range years {
		id := uuid.New()
		startDate := time.Date(y.startYear, time.August, 1, 0, 0, 0, 0, time.UTC)
		endDate := time.Date(y.startYear+1, time.July, 31, 0, 0, 0, 0, time.UTC)

		_, err := s.pool.Exec(ctx, `
			INSERT INTO school_years (id, name, start_date, end_date, is_current, teacher_id)
			VALUES ($1, $2, $3, $4, $5, $6)
			ON CONFLICT DO NOTHING
		`, id, y.name, startDate, endDate, y.isCurrent, s.teacherID)

		if err != nil {
			return nil, err
		}

		results = append(results, SchoolYearResult{
			ID:        id,
			Name:      y.name,
			StartDate: startDate,
			EndDate:   endDate,
			IsCurrent: y.isCurrent,
		})
	}

	return results, nil
}

func (s *Seeder) seedSubjects(ctx context.Context) (map[string]uuid.UUID, error) {
	subjectIDs := make(map[string]uuid.UUID)

	for _, subj := range subjects {
		id := uuid.New()
		_, err := s.pool.Exec(ctx, `
			INSERT INTO subjects (id, teacher_id, name, short_name, is_main_subject)
			VALUES ($1, $2, $3, $4, $5)
			ON CONFLICT (teacher_id, name) DO UPDATE SET id = subjects.id
			RETURNING id
		`, id, s.teacherID, subj.Name, subj.ShortName, subj.IsMain)

		if err != nil {
			return nil, err
		}
		subjectIDs[subj.Name] = id
	}

	return subjectIDs, nil
}

// ClassResult holds created class info
type ClassResult struct {
	ID         uuid.UUID
	Name       string
	GradeLevel int
}

func (s *Seeder) seedClassesForYear(ctx context.Context, sy SchoolYearResult) ([]ClassResult, error) {
	// Classes: 5a, 5b, 6a, 6b, 7a, 7b
	classConfigs := []struct {
		name       string
		gradeLevel int
	}{
		{"5a", 5}, {"5b", 5},
		{"6a", 6}, {"6b", 6},
		{"7a", 7}, {"7b", 7},
	}

	var results []ClassResult
	for _, c := range classConfigs {
		id := uuid.New()
		_, err := s.pool.Exec(ctx, `
			INSERT INTO classes (id, teacher_id, school_year_id, name, grade_level, school_type, federal_state)
			VALUES ($1, $2, $3, $4, $5, $6, $7)
			ON CONFLICT (teacher_id, school_year_id, name) DO UPDATE SET id = classes.id
		`, id, s.teacherID, sy.ID, c.name, c.gradeLevel, "gymnasium", "niedersachsen")

		if err != nil {
			return nil, err
		}

		results = append(results, ClassResult{
			ID:         id,
			Name:       c.name,
			GradeLevel: c.gradeLevel,
		})
	}

	return results, nil
}

// 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
}
