package services

import (
	"testing"

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

func TestGradeService_CalculateFinalGrade(t *testing.T) {
	tests := []struct {
		name           string
		writtenAvg     float64
		oralGrade      float64
		writtenWeight  int
		oralWeight     int
		expectedFinal  float64
	}{
		{
			name:           "standard weights 60/40 - same grades",
			writtenAvg:     2.0,
			oralGrade:      2.0,
			writtenWeight:  60,
			oralWeight:     40,
			expectedFinal:  2.0,
		},
		{
			name:           "standard weights 60/40 - different grades",
			writtenAvg:     2.0,
			oralGrade:      3.0,
			writtenWeight:  60,
			oralWeight:     40,
			expectedFinal:  2.4,
		},
		{
			name:           "equal weights 50/50",
			writtenAvg:     2.0,
			oralGrade:      4.0,
			writtenWeight:  50,
			oralWeight:     50,
			expectedFinal:  3.0,
		},
		{
			name:           "hauptfach weights 70/30",
			writtenAvg:     1.5,
			oralGrade:      2.5,
			writtenWeight:  70,
			oralWeight:     30,
			expectedFinal:  1.8,
		},
		{
			name:           "only written (100/0)",
			writtenAvg:     2.5,
			oralGrade:      0,
			writtenWeight:  100,
			oralWeight:     0,
			expectedFinal:  2.5,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			finalGrade := calculateFinalGradeWeighted(tt.writtenAvg, tt.oralGrade, tt.writtenWeight, tt.oralWeight)
			assert.InDelta(t, tt.expectedFinal, finalGrade, 0.01)
		})
	}
}

func TestGradeService_ValidateOralGrade(t *testing.T) {
	tests := []struct {
		name    string
		grade   float64
		wantErr bool
	}{
		{
			name:    "valid grade 1.0",
			grade:   1.0,
			wantErr: false,
		},
		{
			name:    "valid grade 2.5",
			grade:   2.5,
			wantErr: false,
		},
		{
			name:    "valid grade 6.0",
			grade:   6.0,
			wantErr: false,
		},
		{
			name:    "invalid grade - too low",
			grade:   0.5,
			wantErr: true,
		},
		{
			name:    "invalid grade - too high",
			grade:   6.5,
			wantErr: true,
		},
		{
			name:    "invalid grade - negative",
			grade:   -1.0,
			wantErr: true,
		},
	}

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

func TestGradeService_ValidateWeights(t *testing.T) {
	tests := []struct {
		name          string
		writtenWeight int
		oralWeight    int
		wantErr       bool
	}{
		{
			name:          "valid 60/40",
			writtenWeight: 60,
			oralWeight:    40,
			wantErr:       false,
		},
		{
			name:          "valid 50/50",
			writtenWeight: 50,
			oralWeight:    50,
			wantErr:       false,
		},
		{
			name:          "valid 100/0",
			writtenWeight: 100,
			oralWeight:    0,
			wantErr:       false,
		},
		{
			name:          "invalid - sum not 100",
			writtenWeight: 60,
			oralWeight:    50,
			wantErr:       true,
		},
		{
			name:          "invalid - negative weight",
			writtenWeight: -10,
			oralWeight:    110,
			wantErr:       true,
		},
		{
			name:          "invalid - both zero",
			writtenWeight: 0,
			oralWeight:    0,
			wantErr:       true,
		},
	}

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

func TestGradeService_CalculateWrittenAverage(t *testing.T) {
	tests := []struct {
		name        string
		grades      []float64
		expectedAvg float64
	}{
		{
			name:        "single grade",
			grades:      []float64{2.0},
			expectedAvg: 2.0,
		},
		{
			name:        "two grades - same",
			grades:      []float64{2.0, 2.0},
			expectedAvg: 2.0,
		},
		{
			name:        "two grades - different",
			grades:      []float64{1.0, 3.0},
			expectedAvg: 2.0,
		},
		{
			name:        "multiple grades",
			grades:      []float64{1.0, 2.0, 3.0, 4.0},
			expectedAvg: 2.5,
		},
		{
			name:        "empty grades",
			grades:      []float64{},
			expectedAvg: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			avg := calculateWrittenAverage(tt.grades)
			assert.InDelta(t, tt.expectedAvg, avg, 0.01)
		})
	}
}

func TestGradeService_RoundGrade(t *testing.T) {
	tests := []struct {
		name          string
		grade         float64
		expectedRound float64
	}{
		{
			name:          "exact 2.0",
			grade:         2.0,
			expectedRound: 2.0,
		},
		{
			name:          "2.33 rounds to 2.3",
			grade:         2.33,
			expectedRound: 2.3,
		},
		{
			name:          "2.35 rounds to 2.4",
			grade:         2.35,
			expectedRound: 2.4,
		},
		{
			name:          "2.44 rounds to 2.4",
			grade:         2.44,
			expectedRound: 2.4,
		},
		{
			name:          "2.45 rounds to 2.5",
			grade:         2.45,
			expectedRound: 2.5,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rounded := roundGrade(tt.grade)
			assert.InDelta(t, tt.expectedRound, rounded, 0.01)
		})
	}
}

func TestGradeService_GradeToOberstufenPoints(t *testing.T) {
	tests := []struct {
		name           string
		grade          float64
		expectedPoints int
	}{
		{
			name:           "Grade 1.0 = 15 points",
			grade:          1.0,
			expectedPoints: 15,
		},
		{
			name:           "Grade 1.3 = 14 points",
			grade:          1.3,
			expectedPoints: 14,
		},
		{
			name:           "Grade 2.0 = 11 points",
			grade:          2.0,
			expectedPoints: 11,
		},
		{
			name:           "Grade 3.0 = 8 points",
			grade:          3.0,
			expectedPoints: 8,
		},
		{
			name:           "Grade 4.0 = 5 points",
			grade:          4.0,
			expectedPoints: 5,
		},
		{
			name:           "Grade 5.0 = 2 points",
			grade:          5.0,
			expectedPoints: 2,
		},
		{
			name:           "Grade 6.0 = 0 points",
			grade:          6.0,
			expectedPoints: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			points := gradeToOberstufenPoints(tt.grade)
			assert.Equal(t, tt.expectedPoints, points)
		})
	}
}

func TestGradeService_OberstufenPointsToGrade(t *testing.T) {
	tests := []struct {
		name          string
		points        int
		expectedGrade float64
	}{
		{
			name:          "15 points = Grade 1.0",
			points:        15,
			expectedGrade: 1.0,
		},
		{
			name:          "12 points = Grade 1.7",
			points:        12,
			expectedGrade: 1.7,
		},
		{
			name:          "10 points = Grade 2.3",
			points:        10,
			expectedGrade: 2.3,
		},
		{
			name:          "5 points = Grade 4.0",
			points:        5,
			expectedGrade: 4.0,
		},
		{
			name:          "0 points = Grade 6.0",
			points:        0,
			expectedGrade: 6.0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			grade := oberstufenPointsToGrade(tt.points)
			assert.InDelta(t, tt.expectedGrade, grade, 0.1)
		})
	}
}

// Helper functions for tests
func calculateFinalGradeWeighted(writtenAvg, oralGrade float64, writtenWeight, oralWeight int) float64 {
	if writtenWeight+oralWeight == 0 {
		return 0
	}
	return (writtenAvg*float64(writtenWeight) + oralGrade*float64(oralWeight)) / float64(writtenWeight+oralWeight)
}

func validateOralGrade(grade float64) error {
	if grade < 1.0 || grade > 6.0 {
		return assert.AnError
	}
	return nil
}

func validateWeights(writtenWeight, oralWeight int) error {
	if writtenWeight < 0 || oralWeight < 0 {
		return assert.AnError
	}
	if writtenWeight+oralWeight != 100 {
		return assert.AnError
	}
	return nil
}

func calculateWrittenAverage(grades []float64) float64 {
	if len(grades) == 0 {
		return 0
	}
	sum := 0.0
	for _, g := range grades {
		sum += g
	}
	return sum / float64(len(grades))
}

func roundGrade(grade float64) float64 {
	return float64(int(grade*10+0.5)) / 10
}

func gradeToOberstufenPoints(grade float64) int {
	// German grade to Oberstufen points conversion
	// 1.0 = 15, 1.3 = 14, 1.7 = 13, 2.0 = 11, etc.
	points := int(17 - (grade * 3))
	if grade < 2.0 {
		points++
	}
	if points > 15 {
		points = 15
	}
	if points < 0 {
		points = 0
	}
	return points
}

func oberstufenPointsToGrade(points int) float64 {
	// Oberstufen points to grade conversion
	if points >= 15 {
		return 1.0
	}
	if points <= 0 {
		return 6.0
	}
	return float64(17-points) / 3.0
}

func TestGradeService_GradeApprovalWorkflow(t *testing.T) {
	tests := []struct {
		name           string
		initialStatus  string
		action         string
		expectedStatus string
		wantErr        bool
	}{
		{
			name:           "pending to approved",
			initialStatus:  "pending",
			action:         "approve",
			expectedStatus: "approved",
			wantErr:        false,
		},
		{
			name:           "pending to locked",
			initialStatus:  "pending",
			action:         "lock",
			expectedStatus: "",
			wantErr:        true,
		},
		{
			name:           "approved to locked",
			initialStatus:  "approved",
			action:         "lock",
			expectedStatus: "locked",
			wantErr:        false,
		},
		{
			name:           "locked cannot be changed",
			initialStatus:  "locked",
			action:         "approve",
			expectedStatus: "",
			wantErr:        true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			newStatus, err := processGradeAction(tt.initialStatus, tt.action)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expectedStatus, newStatus)
			}
		})
	}
}

func processGradeAction(currentStatus, action string) (string, error) {
	transitions := map[string]map[string]string{
		"pending": {
			"approve": "approved",
		},
		"approved": {
			"lock":   "locked",
			"reject": "pending",
		},
		"locked": {},
	}

	actions, exists := transitions[currentStatus]
	if !exists {
		return "", assert.AnError
	}

	newStatus, valid := actions[action]
	if !valid {
		return "", assert.AnError
	}

	return newStatus, nil
}
