package services

import (
	"testing"
	"time"

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

func TestGradebookService_ValidateAttendanceStatus(t *testing.T) {
	tests := []struct {
		name    string
		status  string
		wantErr bool
	}{
		{
			name:    "valid - present",
			status:  "present",
			wantErr: false,
		},
		{
			name:    "valid - absent_excused",
			status:  "absent_excused",
			wantErr: false,
		},
		{
			name:    "valid - absent_unexcused",
			status:  "absent_unexcused",
			wantErr: false,
		},
		{
			name:    "valid - late",
			status:  "late",
			wantErr: false,
		},
		{
			name:    "invalid status",
			status:  "invalid",
			wantErr: true,
		},
		{
			name:    "empty status",
			status:  "",
			wantErr: true,
		},
	}

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

func TestGradebookService_ValidateEntryType(t *testing.T) {
	tests := []struct {
		name      string
		entryType string
		wantErr   bool
	}{
		{
			name:      "valid - note",
			entryType: "note",
			wantErr:   false,
		},
		{
			name:      "valid - warning",
			entryType: "warning",
			wantErr:   false,
		},
		{
			name:      "valid - praise",
			entryType: "praise",
			wantErr:   false,
		},
		{
			name:      "valid - incident",
			entryType: "incident",
			wantErr:   false,
		},
		{
			name:      "valid - homework",
			entryType: "homework",
			wantErr:   false,
		},
		{
			name:      "invalid entry type",
			entryType: "invalid",
			wantErr:   true,
		},
		{
			name:      "empty entry type",
			entryType: "",
			wantErr:   true,
		},
	}

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

func TestGradebookService_ValidateAttendanceInput(t *testing.T) {
	tests := []struct {
		name      string
		studentID uuid.UUID
		date      time.Time
		status    string
		periods   int
		wantErr   bool
	}{
		{
			name:      "valid attendance",
			studentID: uuid.New(),
			date:      time.Now(),
			status:    "absent_excused",
			periods:   2,
			wantErr:   false,
		},
		{
			name:      "zero periods",
			studentID: uuid.New(),
			date:      time.Now(),
			status:    "absent_excused",
			periods:   0,
			wantErr:   true,
		},
		{
			name:      "negative periods",
			studentID: uuid.New(),
			date:      time.Now(),
			status:    "absent_excused",
			periods:   -1,
			wantErr:   true,
		},
		{
			name:      "too many periods",
			studentID: uuid.New(),
			date:      time.Now(),
			status:    "absent_excused",
			periods:   15,
			wantErr:   true,
		},
		{
			name:      "nil student ID",
			studentID: uuid.Nil,
			date:      time.Now(),
			status:    "absent_excused",
			periods:   2,
			wantErr:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateAttendanceInput(tt.studentID, tt.date, tt.status, tt.periods)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestGradebookService_ValidateGradebookEntry(t *testing.T) {
	tests := []struct {
		name      string
		classID   uuid.UUID
		entryType string
		content   string
		wantErr   bool
	}{
		{
			name:      "valid entry",
			classID:   uuid.New(),
			entryType: "note",
			content:   "Today we discussed the French Revolution",
			wantErr:   false,
		},
		{
			name:      "empty content",
			classID:   uuid.New(),
			entryType: "note",
			content:   "",
			wantErr:   true,
		},
		{
			name:      "invalid entry type",
			classID:   uuid.New(),
			entryType: "invalid",
			content:   "Some content",
			wantErr:   true,
		},
		{
			name:      "nil class ID",
			classID:   uuid.Nil,
			entryType: "note",
			content:   "Some content",
			wantErr:   true,
		},
	}

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

func TestGradebookService_CalculateAbsenceDays(t *testing.T) {
	tests := []struct {
		name            string
		attendances     []attendanceRecord
		expectedTotal   int
		expectedExcused int
	}{
		{
			name: "no absences",
			attendances: []attendanceRecord{
				{Status: "present", Periods: 6},
				{Status: "present", Periods: 6},
			},
			expectedTotal:   0,
			expectedExcused: 0,
		},
		{
			name: "excused absences only",
			attendances: []attendanceRecord{
				{Status: "absent_excused", Periods: 6},
				{Status: "absent_excused", Periods: 6},
			},
			expectedTotal:   2,
			expectedExcused: 2,
		},
		{
			name: "unexcused absences only",
			attendances: []attendanceRecord{
				{Status: "absent_unexcused", Periods: 6},
			},
			expectedTotal:   1,
			expectedExcused: 0,
		},
		{
			name: "mixed absences",
			attendances: []attendanceRecord{
				{Status: "absent_excused", Periods: 6},
				{Status: "absent_unexcused", Periods: 6},
				{Status: "present", Periods: 6},
			},
			expectedTotal:   2,
			expectedExcused: 1,
		},
		{
			name: "late arrivals not counted",
			attendances: []attendanceRecord{
				{Status: "late", Periods: 1},
				{Status: "late", Periods: 1},
			},
			expectedTotal:   0,
			expectedExcused: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			total, excused := calculateAbsenceDays(tt.attendances)
			assert.Equal(t, tt.expectedTotal, total)
			assert.Equal(t, tt.expectedExcused, excused)
		})
	}
}

func TestGradebookService_DateRangeValidation(t *testing.T) {
	tests := []struct {
		name      string
		startDate time.Time
		endDate   time.Time
		wantErr   bool
	}{
		{
			name:      "valid range - same day",
			startDate: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
			endDate:   time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
			wantErr:   false,
		},
		{
			name:      "valid range - week",
			startDate: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
			endDate:   time.Date(2024, 1, 22, 0, 0, 0, 0, time.UTC),
			wantErr:   false,
		},
		{
			name:      "invalid range - end before start",
			startDate: time.Date(2024, 1, 22, 0, 0, 0, 0, time.UTC),
			endDate:   time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
			wantErr:   true,
		},
	}

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

// Helper types and functions
type attendanceRecord struct {
	Status  string
	Periods int
}

func validateAttendanceStatus(status string) error {
	validStatuses := map[string]bool{
		"present":          true,
		"absent_excused":   true,
		"absent_unexcused": true,
		"late":             true,
	}
	if !validStatuses[status] {
		return assert.AnError
	}
	return nil
}

func validateEntryType(entryType string) error {
	validTypes := map[string]bool{
		"note":     true,
		"warning":  true,
		"praise":   true,
		"incident": true,
		"homework": true,
	}
	if !validTypes[entryType] {
		return assert.AnError
	}
	return nil
}

func validateAttendanceInput(studentID uuid.UUID, date time.Time, status string, periods int) error {
	if studentID == uuid.Nil {
		return assert.AnError
	}
	if periods <= 0 || periods > 12 {
		return assert.AnError
	}
	return validateAttendanceStatus(status)
}

func validateGradebookEntry(classID uuid.UUID, entryType, content string) error {
	if classID == uuid.Nil {
		return assert.AnError
	}
	if content == "" {
		return assert.AnError
	}
	return validateEntryType(entryType)
}

func calculateAbsenceDays(attendances []attendanceRecord) (total, excused int) {
	for _, a := range attendances {
		if a.Status == "absent_excused" {
			total++
			excused++
		} else if a.Status == "absent_unexcused" {
			total++
		}
	}
	return total, excused
}

func validateDateRange(start, end time.Time) error {
	if end.Before(start) {
		return assert.AnError
	}
	return nil
}

func TestGradebookService_BulkAttendanceValidation(t *testing.T) {
	tests := []struct {
		name        string
		studentIDs  []uuid.UUID
		date        time.Time
		status      string
		wantErr     bool
		errCount    int
	}{
		{
			name:        "all valid",
			studentIDs:  []uuid.UUID{uuid.New(), uuid.New(), uuid.New()},
			date:        time.Now(),
			status:      "present",
			wantErr:     false,
			errCount:    0,
		},
		{
			name:        "empty list",
			studentIDs:  []uuid.UUID{},
			date:        time.Now(),
			status:      "present",
			wantErr:     true,
			errCount:    1,
		},
		{
			name:        "contains nil UUID",
			studentIDs:  []uuid.UUID{uuid.New(), uuid.Nil, uuid.New()},
			date:        time.Now(),
			status:      "present",
			wantErr:     true,
			errCount:    1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			errs := validateBulkAttendance(tt.studentIDs, tt.date, tt.status)
			if tt.wantErr {
				assert.Len(t, errs, tt.errCount)
			} else {
				assert.Empty(t, errs)
			}
		})
	}
}

func validateBulkAttendance(studentIDs []uuid.UUID, date time.Time, status string) []error {
	var errs []error
	if len(studentIDs) == 0 {
		errs = append(errs, assert.AnError)
		return errs
	}
	for _, id := range studentIDs {
		if id == uuid.Nil {
			errs = append(errs, assert.AnError)
		}
	}
	if err := validateAttendanceStatus(status); err != nil {
		errs = append(errs, err)
	}
	return errs
}
