package services

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestExamService_ValidateExamInput(t *testing.T) {
	tests := []struct {
		name            string
		title           string
		examType        string
		durationMinutes int
		maxPoints       float64
		wantErr         bool
	}{
		{
			name:            "valid klassenarbeit",
			title:           "Mathematik Klassenarbeit Nr. 1",
			examType:        "klassenarbeit",
			durationMinutes: 45,
			maxPoints:       50,
			wantErr:         false,
		},
		{
			name:            "valid test",
			title:           "Vokabeltest Englisch",
			examType:        "test",
			durationMinutes: 20,
			maxPoints:       20,
			wantErr:         false,
		},
		{
			name:            "valid klausur",
			title:           "Oberstufen-Klausur Deutsch",
			examType:        "klausur",
			durationMinutes: 180,
			maxPoints:       100,
			wantErr:         false,
		},
		{
			name:            "empty title",
			title:           "",
			examType:        "klassenarbeit",
			durationMinutes: 45,
			maxPoints:       50,
			wantErr:         true,
		},
		{
			name:            "invalid exam type",
			title:           "Test",
			examType:        "invalid_type",
			durationMinutes: 45,
			maxPoints:       50,
			wantErr:         true,
		},
		{
			name:            "negative duration",
			title:           "Test",
			examType:        "test",
			durationMinutes: -10,
			maxPoints:       50,
			wantErr:         true,
		},
		{
			name:            "zero max points",
			title:           "Test",
			examType:        "test",
			durationMinutes: 45,
			maxPoints:       0,
			wantErr:         true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateExamInput(tt.title, tt.examType, tt.durationMinutes, tt.maxPoints)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestExamService_ValidateExamResult(t *testing.T) {
	tests := []struct {
		name           string
		pointsAchieved float64
		maxPoints      float64
		wantErr        bool
	}{
		{
			name:           "valid result - full points",
			pointsAchieved: 50,
			maxPoints:      50,
			wantErr:        false,
		},
		{
			name:           "valid result - partial points",
			pointsAchieved: 35.5,
			maxPoints:      50,
			wantErr:        false,
		},
		{
			name:           "valid result - zero points",
			pointsAchieved: 0,
			maxPoints:      50,
			wantErr:        false,
		},
		{
			name:           "invalid result - negative points",
			pointsAchieved: -5,
			maxPoints:      50,
			wantErr:        true,
		},
		{
			name:           "invalid result - exceeds max",
			pointsAchieved: 55,
			maxPoints:      50,
			wantErr:        true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateExamResult(tt.pointsAchieved, tt.maxPoints)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestExamService_CalculateGrade(t *testing.T) {
	tests := []struct {
		name           string
		pointsAchieved float64
		maxPoints      float64
		expectedGrade  float64
	}{
		{
			name:           "100% - Grade 1",
			pointsAchieved: 50,
			maxPoints:      50,
			expectedGrade:  1.0,
		},
		{
			name:           "92% - Grade 1",
			pointsAchieved: 46,
			maxPoints:      50,
			expectedGrade:  1.0,
		},
		{
			name:           "85% - Grade 2",
			pointsAchieved: 42.5,
			maxPoints:      50,
			expectedGrade:  2.0,
		},
		{
			name:           "70% - Grade 3",
			pointsAchieved: 35,
			maxPoints:      50,
			expectedGrade:  3.0,
		},
		{
			name:           "55% - Grade 4",
			pointsAchieved: 27.5,
			maxPoints:      50,
			expectedGrade:  4.0,
		},
		{
			name:           "40% - Grade 5",
			pointsAchieved: 20,
			maxPoints:      50,
			expectedGrade:  5.0,
		},
		{
			name:           "20% - Grade 6",
			pointsAchieved: 10,
			maxPoints:      50,
			expectedGrade:  6.0,
		},
		{
			name:           "0% - Grade 6",
			pointsAchieved: 0,
			maxPoints:      50,
			expectedGrade:  6.0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			grade := calculateGrade(tt.pointsAchieved, tt.maxPoints)
			assert.Equal(t, tt.expectedGrade, grade)
		})
	}
}

func TestExamService_CalculatePercentage(t *testing.T) {
	tests := []struct {
		name               string
		pointsAchieved     float64
		maxPoints          float64
		expectedPercentage float64
	}{
		{
			name:               "100%",
			pointsAchieved:     50,
			maxPoints:          50,
			expectedPercentage: 100.0,
		},
		{
			name:               "50%",
			pointsAchieved:     25,
			maxPoints:          50,
			expectedPercentage: 50.0,
		},
		{
			name:               "0%",
			pointsAchieved:     0,
			maxPoints:          50,
			expectedPercentage: 0.0,
		},
		{
			name:               "33.33%",
			pointsAchieved:     10,
			maxPoints:          30,
			expectedPercentage: 33.33,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			percentage := calculatePercentage(tt.pointsAchieved, tt.maxPoints)
			assert.InDelta(t, tt.expectedPercentage, percentage, 0.01)
		})
	}
}

func TestExamService_DetermineNeedsRewrite(t *testing.T) {
	tests := []struct {
		name         string
		grade        float64
		needsRewrite bool
	}{
		{
			name:         "Grade 1 - no rewrite",
			grade:        1.0,
			needsRewrite: false,
		},
		{
			name:         "Grade 4 - no rewrite",
			grade:        4.0,
			needsRewrite: false,
		},
		{
			name:         "Grade 5 - needs rewrite",
			grade:        5.0,
			needsRewrite: true,
		},
		{
			name:         "Grade 6 - needs rewrite",
			grade:        6.0,
			needsRewrite: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := determineNeedsRewrite(tt.grade)
			assert.Equal(t, tt.needsRewrite, result)
		})
	}
}

// Validation helper functions
func validateExamInput(title, examType string, durationMinutes int, maxPoints float64) error {
	if title == "" {
		return assert.AnError
	}
	validTypes := map[string]bool{
		"klassenarbeit": true,
		"test":          true,
		"klausur":       true,
	}
	if !validTypes[examType] {
		return assert.AnError
	}
	if durationMinutes <= 0 {
		return assert.AnError
	}
	if maxPoints <= 0 {
		return assert.AnError
	}
	return nil
}

func validateExamResult(pointsAchieved, maxPoints float64) error {
	if pointsAchieved < 0 {
		return assert.AnError
	}
	if pointsAchieved > maxPoints {
		return assert.AnError
	}
	return nil
}

func calculateGrade(pointsAchieved, maxPoints float64) float64 {
	percentage := (pointsAchieved / maxPoints) * 100

	switch {
	case percentage >= 92:
		return 1.0
	case percentage >= 81:
		return 2.0
	case percentage >= 67:
		return 3.0
	case percentage >= 50:
		return 4.0
	case percentage >= 30:
		return 5.0
	default:
		return 6.0
	}
}

func calculatePercentage(pointsAchieved, maxPoints float64) float64 {
	if maxPoints == 0 {
		return 0
	}
	result := (pointsAchieved / maxPoints) * 100
	// Round to 2 decimal places
	return float64(int(result*100)) / 100
}

func determineNeedsRewrite(grade float64) bool {
	return grade >= 5.0
}

func TestExamService_ExamDateValidation(t *testing.T) {
	tests := []struct {
		name     string
		examDate time.Time
		wantErr  bool
	}{
		{
			name:     "future date - valid",
			examDate: time.Now().AddDate(0, 0, 7),
			wantErr:  false,
		},
		{
			name:     "today - valid",
			examDate: time.Now(),
			wantErr:  false,
		},
		{
			name:     "past date - valid for recording",
			examDate: time.Now().AddDate(0, 0, -7),
			wantErr:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateExamDate(tt.examDate)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func validateExamDate(date time.Time) error {
	// Exam dates are always valid as we need to record past exams too
	return nil
}

func TestExamService_ExamStatusTransition(t *testing.T) {
	tests := []struct {
		name          string
		currentStatus string
		newStatus     string
		valid         bool
	}{
		{
			name:          "draft to active",
			currentStatus: "draft",
			newStatus:     "active",
			valid:         true,
		},
		{
			name:          "active to archived",
			currentStatus: "active",
			newStatus:     "archived",
			valid:         true,
		},
		{
			name:          "draft to archived",
			currentStatus: "draft",
			newStatus:     "archived",
			valid:         true,
		},
		{
			name:          "archived to active - invalid",
			currentStatus: "archived",
			newStatus:     "active",
			valid:         false,
		},
		{
			name:          "archived to draft - invalid",
			currentStatus: "archived",
			newStatus:     "draft",
			valid:         false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isValidStatusTransition(tt.currentStatus, tt.newStatus)
			assert.Equal(t, tt.valid, result)
		})
	}
}

func isValidStatusTransition(current, new string) bool {
	transitions := map[string][]string{
		"draft":    {"active", "archived"},
		"active":   {"archived"},
		"archived": {},
	}

	allowed, exists := transitions[current]
	if !exists {
		return false
	}

	for _, s := range allowed {
		if s == new {
			return true
		}
	}
	return false
}
