//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
//Copyright (C) 2024 HTWKalender support@htwkalender.de

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU Affero General Public License for more details.

//You should have received a copy of the GNU Affero General Public License
//along with this program.  If not, see <https://www.gnu.org/licenses/>.

package events

import (
	"github.com/pocketbase/pocketbase"
	"htwkalender/data-manager/model"
	"htwkalender/data-manager/service/db"
	"htwkalender/data-manager/service/fetch/v1"
	"htwkalender/data-manager/service/functions"
	"log/slog"
	"strconv"
)

type EventService interface {
	GetModulesForCourseDistinct(course string, semester string) (model.Events, error)
	GetAllModulesDistinct() ([]model.ModuleDTO, error)
	GetModuleByUUID(uuid string) (model.Module, error)
	DeleteAllEventsByCourseAndSemester(course string, semester string) error
	DeleteAllEvents() error
	UpdateModulesForCourse(course string) (model.Events, error)
	GetEventTypes() ([]string, error)
}

type Named interface {
	GetName() string
	SetName(name string)
}

type PocketBaseEventService struct {
	app *pocketbase.PocketBase
}

func NewPocketBaseEventService(app *pocketbase.PocketBase) *PocketBaseEventService {
	return &PocketBaseEventService{app: app}
}

func (s *PocketBaseEventService) GetModulesForCourseDistinct(course string, semester string) (model.Events, error) {
	modules, err := db.GetAllModulesForCourse(s.app, course, semester)

	// Convert the []model.Module to []Named
	var namedEvents []Named
	for _, module := range modules {
		namedEvents = append(namedEvents, &module)
	}

	replaceEmptyEntry(namedEvents, "Sonderveranstaltungen")
	return modules, err
}

// replaceEmptyEntry replaces an empty entry in a module with a replacement string
// If the module is not empty, nothing happens
func replaceEmptyEntry(namedList []Named, replacement string) {
	for i, namedItem := range namedList {
		if functions.OnlyWhitespace(namedItem.GetName()) {
			namedList[i].SetName(replacement)
		}
	}
}

// GetAllModulesDistinct returns all modules distinct by name and course from the database
// That means you get all modules with duplicates if they have different courses
func (s *PocketBaseEventService) GetAllModulesDistinct() ([]model.ModuleDTO, error) {
	modules, err := db.GetAllModulesDistinctByNameAndCourse(s.app)
	if err != nil {
		return nil, err
	}
	var namedModules []Named
	for _, module := range modules {
		namedModules = append(namedModules, &module)
	}
	replaceEmptyEntry(namedModules, "Sonderveranstaltungen")
	return modules, nil
}

func (s *PocketBaseEventService) GetModuleByUUID(uuid string) (model.Module, error) {
	module, findModuleErr := db.FindModuleByUUID(s.app, uuid)
	if findModuleErr != nil {
		return model.Module{}, findModuleErr
	}

	events, findEventsError := db.FindAllEventsByModule(s.app, module)
	if findEventsError != nil || len(events) == 0 {
		return model.Module{}, findEventsError
	} else {
		return model.Module{
			UUID:     events[0].UUID,
			Name:     events[0].Name,
			Events:   events,
			Prof:     events[0].Prof,
			Course:   events[0].Course,
			Semester: events[0].Semester,
		}, nil
	}
}

// DeleteAllEventsByCourseAndSemester deletes all events for a course and a semester
// If the deletion was successful, nil is returned
// If the deletion was not successful, an error is returned
func (s *PocketBaseEventService) DeleteAllEventsByCourseAndSemester(course string, semester string) error {
	err := db.DeleteAllEventsByCourse(s.app.Dao(), course, semester)
	if err != nil {
		return err
	} else {
		return nil
	}
}

func (s *PocketBaseEventService) DeleteAllEvents() error {
	err := db.DeleteAllEvents(s.app)
	if err != nil {
		return err
	} else {
		return nil
	}
}

// UpdateModulesForCourse updates all modules for a course
// Does Updates for ws and ss semester sequentially
// Update runs through the following steps:
// 1. Delete all events for the course and the semester
// 2. Fetch all events for the course and the semester
// 3. Save all events for the course and the semester
// If the update was successful, nil is returned
// If the update was not successful, an error is returned
func (s *PocketBaseEventService) UpdateModulesForCourse(course string) (model.Events, error) {

	seminarGroup, err := v1.GetSeminarGroupEventsFromHTML(course)
	if err != nil {
		return nil, err
	}

	seminarGroup = v1.ClearEmptySeminarGroups(seminarGroup)
	seminarGroup = v1.ReplaceEmptyEventNames(seminarGroup)

	//check if events in the seminarGroups Events are already in the database
	//if yes, keep the database as it is
	//if no, delete all events for the course and the semester and save the new events
	//if there are no events in the database, save the new events

	//get all events for the course and the semester
	dbEvents, err := db.GetAllEventsForCourse(s.app, course)
	if err != nil {
		return nil, err
	}

	//if there are no events in the database, save the new events
	if len(dbEvents) == 0 {
		events, dbError := db.SaveSeminarGroupEvents(seminarGroup, s.app)
		if dbError != nil {
			return nil, dbError
		}
		return events, nil
	}

	// Create partial update list and delete list for the events
	var insertList model.Events
	var deleteList model.Events

	// check which events are not already in the database and need to be inserted/saved
	for _, event := range seminarGroup.Events {
		if !containsEvent(dbEvents, event) {
			insertList = append(insertList, event)
		}
	}

	// check which events are in the database but not in the seminarGroup and need to be deleted
	for _, dbEvent := range dbEvents {
		if !containsEvent(seminarGroup.Events, dbEvent) {
			deleteList = append(deleteList, dbEvent)
		}
	}

	// delete all events that are in the deleteList
	err = db.DeleteEvents(deleteList, s.app)
	if err != nil {
		slog.Error("Failed to delete events:", "error", err)
		return nil, err
	}

	// save all events that are in the insertList
	savedEvents, err := db.SaveEvents(insertList, s.app.Dao())
	if err != nil {
		slog.Error("Failed to save events: ", "error", err)
		return nil, err
	}

	slog.Info("Course: " + course + " - Event changes: " + strconv.FormatInt(int64(len(insertList)), 10) + " new events, " + strconv.FormatInt(int64(len(deleteList)), 10) + " deleted events")
	return savedEvents, nil
}

func containsEvent(events model.Events, event model.Event) bool {
	for _, e := range events {
		if e.Name == event.Name &&
			e.Prof == event.Prof &&
			e.Rooms == event.Rooms &&
			e.Semester == event.Semester &&
			e.Start == event.Start &&
			e.End == event.End &&
			e.Course == event.Course {
			return true
		}
	}
	return false
}

func (s *PocketBaseEventService) GetEventTypes() ([]string, error) {
	dbEventTypes, err := db.GetAllEventTypes(s.app)
	if err != nil {
		return nil, err
	}

	// Convert the []model.EventType to []string
	var eventTypes []string
	for _, eventType := range dbEventTypes {
		eventTypes = append(eventTypes, eventType.EventType)
	}

	return eventTypes, nil
}
