package course

import (
	"bytes"
	"fmt"
	"github.com/stretchr/testify/require"
	"htwkalender/data-manager/model"
	"htwkalender/data-manager/model/serviceModel"
	"htwkalender/data-manager/service/events/mock"
	"htwkalender/data-manager/service/functions/time"
	"log/slog"
	"regexp"
	"testing"
)

// CustomWriter is a custom writer to capture log output
type CustomWriter struct {
	Buffer bytes.Buffer
}

func (w *CustomWriter) Write(p []byte) (n int, err error) {
	return w.Buffer.Write(p)
}

func TestUpdateCourse(t *testing.T) {
	// Create mock services
	mockCourseService := new(mock.MockCourseService)
	mockEventService := new(mock.MockEventService)

	// Set the current time to 2024-07-01T00:00:00Z
	// This will result in the current semester being "ss"
	parseTime, _ := time.ParseTime("2024-07-01T00:00:00Z")
	mockClock := time.MockClock{NowTime: parseTime}

	events := model.Events{}

	// Set up expectations
	mockCourseService.On("GetAllCoursesForSemester", "ss").Return([]model.SeminarGroup{{Course: "Course1", Semester: ""}, {Course: "Course2", Semester: ""}})
	mockEventService.On("UpdateModulesForCourse", model.SeminarGroup{Course: "Course1", Semester: ""}).Return(events, nil)
	mockEventService.On("UpdateModulesForCourse", model.SeminarGroup{Course: "Course2", Semester: ""}).Return(events, nil)

	// Inject mocks into the UpdateCourse function
	service := serviceModel.Service{
		CourseService: mockCourseService,
		EventService:  mockEventService,
		App:           nil,
	}
	UpdateCourse(service, mockClock)

	// Assert that the expectations were met
	mockCourseService.AssertExpectations(t)
	mockEventService.AssertExpectations(t)

	// Assert that the UpdateCourse function was called twice
	mockCourseService.AssertNumberOfCalls(t, "GetAllCoursesForSemester", 1)
	mockEventService.AssertNumberOfCalls(t, "UpdateModulesForCourse", 2)

	// Assert that the UpdateCourse function was called with the correct arguments
	mockEventService.AssertCalled(t, "UpdateModulesForCourse", model.SeminarGroup{Course: "Course1", Semester: ""})
	mockEventService.AssertCalled(t, "UpdateModulesForCourse", model.SeminarGroup{Course: "Course2", Semester: ""})
}

func TestUpdateCourseErr(t *testing.T) {
	// Create mock services
	mockCourseService := new(mock.MockCourseService)
	mockEventService := new(mock.MockEventService)

	events := model.Events{}

	// Set the current time to 2024-07-01T00:00:00Z
	// This will result in the current semester being "ss"
	parseTime, _ := time.ParseTime("2024-07-01T00:00:00Z")
	mockClock := time.MockClock{NowTime: parseTime}

	// Set up expectations
	mockCourseService.On("GetAllCoursesForSemester", "ss").Return([]model.SeminarGroup{{Course: "Course1", Semester: ""}, {Course: "Course2", Semester: ""}})
	mockEventService.On("UpdateModulesForCourse", model.SeminarGroup{Course: "Course1", Semester: ""}).Return(events, fmt.Errorf("error"))
	mockEventService.On("UpdateModulesForCourse", model.SeminarGroup{Course: "Course2", Semester: ""}).Return(events, fmt.Errorf("error"))

	// Create a custom writer to capture log output
	customWriter := &CustomWriter{}
	originalLogger := slog.Default()
	defer slog.SetDefault(originalLogger)

	// Replace the default logger with a custom logger
	slog.SetDefault(slog.New(slog.NewTextHandler(customWriter, nil)))

	// Inject mocks into the UpdateCourse function
	service := serviceModel.Service{
		CourseService: mockCourseService,
		EventService:  mockEventService,
		App:           nil,
	}
	UpdateCourse(service, mockClock)

	// Assert that the expectations were met
	mockCourseService.AssertExpectations(t)
	mockEventService.AssertExpectations(t)

	// Assert that the UpdateCourse function was called twice
	mockCourseService.AssertNumberOfCalls(t, "GetAllCoursesForSemester", 1)
	mockEventService.AssertNumberOfCalls(t, "UpdateModulesForCourse", 2)

	// Check the captured log output for the expected messages
	logOutput := customWriter.Buffer.String()
	require.Regexp(t, regexp.MustCompile(`Update Course: Course1 failed:.*error`), logOutput)
	require.Regexp(t, regexp.MustCompile(`Update Course: Course2 failed:.*error`), logOutput)
}
