package orm

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"gogs.carducci-dante.gov.it/karmen/core/renderer"
)

type Teacher struct {
	Credential

	IsCoordinator bool `gorm:"-"`
	IsMinuter     bool `gorm:"-"`

	Classes     []*Class
	Students    []*Student
	Subjects    []*Subject
	Activities  []*Activity
	Departments []*Department

	Coordinator []*Class
	Minuter     []*Class

	SubjectsByClass map[uint][]*Subject `gorm:"-"`

	Hours       int
	ActualHours int `gorm:"-"`
}

var (
	selectUniqueTeacherClasses = `
SELECT DISTINCT classes.* FROM activities 
INNER JOIN classes on classes.id=activities.class_id 
WHERE (activities.teacher_id=? OR activities.supply_teacher_id=?)`

	selectUniqueTeacherSubjects = `
SELECT DISTINCT subjects.* FROM activities 
INNER JOIN subjects on subjects.id=activities.subject_id 
WHERE (activities.teacher_id=? OR activities.supply_teacher_id=?)`

	selectUniqueTeacherStudents = `
SELECT DISTINCT students.* FROM activities 
INNER JOIN students on students.id=activities.student_id 
WHERE (activities.teacher_id=? OR activities.supply_teacher_id=?)`

	selectUniqueTeacherSubjectsByClass = `
SELECT subjects.* FROM activities 
INNER JOIN subjects on subjects.id=activities.subject_id 
WHERE (activities.teacher_id=? OR activities.supply_teacher_id=?) AND (activities.class_id=?)
`

	selectSupplyTeachers = `
SELECT teachers.*,1 as supply FROM activities 
INNER JOIN teachers on teachers.id=activities.teacher_id 
WHERE supply_teacher_id <> 0 ORDER BY surname,name
`
	countActualTeacherHours = `
 SELECT sum(hours) as actual_hours FROM activities WHERE (teacher_id=? OR supply_teacher_id=?);

`
)

func (t *Teacher) GetID() uint { return t.ID }

func GetTeacher(args map[string]string) (interface{}, error) {
	var teacher Teacher
	if err := DB().First(&teacher, args["id"]).Error; err != nil {
		return nil, err
	}
	return &teacher, nil
}

func GetTeacherAll(args map[string]string) (interface{}, error) {
	var teacher Teacher

	id := args["id"]

	if err := DB().Where("id = ?", id).Find(&teacher).Error; err != nil {
		return nil, err
	}

	if err := DB().Raw(selectUniqueTeacherClasses, id, id).Scan(&teacher.Classes).Error; err != nil {
		return nil, err
	}

	if err := DB().Raw(selectUniqueTeacherSubjects, id, id).Scan(&teacher.Subjects).Error; err != nil {
		return nil, err
	}

	if err := DB().Raw(selectUniqueTeacherStudents, id, id).Scan(&teacher.Students).Error; err != nil {
		return nil, err
	}

	var result struct {
		ActualHours int
	}

	if err := DB().Raw(countActualTeacherHours, id, id).Scan(&result).Error; err != nil {
		return nil, err
	}
	teacher.ActualHours = result.ActualHours

	if err := DB().Preload("SupplyTeacher").Preload("Teacher").Preload("Subject").Preload("Student").Preload("Class").Where("teacher_id=? OR supply_teacher_id=?", id, id).Find(&teacher.Activities).Error; err != nil {
		return nil, err
	}

	coordClasses, err := teacher.CoordinatorClasses()
	if err != nil {
		return nil, err
	}

	minuterClasses, err := teacher.MinuterClasses()
	if err != nil {
		return nil, err
	}

	teacher.IsCoordinator, teacher.IsMinuter = len(coordClasses) > 0, len(minuterClasses) > 0
	teacher.Coordinator, teacher.Minuter = coordClasses, minuterClasses

	return &teacher, nil
}

func GetTeachers(args map[string]string) (interface{}, error) {
	var teachers []*Teacher
	if err := DB().Order("surname,name").Find(&teachers).Error; err != nil {
		return nil, err
	}
	return teachers, nil
}

func GetTeachersAll(args map[string]string) (interface{}, error) {
	var teachers []*Teacher
	if err := DB().Order("surname,name").Find(&teachers).Error; err != nil {
		return nil, err
	}
	for _, teacher := range teachers {
		teacher.GetClasses()
	}
	for _, teacher := range teachers {
		teacher.GetSubjects()
	}
	for _, teacher := range teachers {
		teacher.GetStudents()
	}
	for _, teacher := range teachers {
		teacher.GetActivities()
	}

	for _, teacher := range teachers {
		teacher.SubjectsByClass = make(map[uint][]*Subject)
		for _, c := range teacher.Classes {
			subjects, err := teacher.GetSubjectsByClassID(strconv.Itoa(int(c.ID)))
			if err != nil {
				return nil, err
			}
			teacher.SubjectsByClass[c.ID] = subjects
		}

		coordClasses, err := teacher.CoordinatorClasses()
		if err != nil {
			return nil, err
		}

		minuterClasses, err := teacher.MinuterClasses()
		if err != nil {
			return nil, err
		}

		teacher.Coordinator, teacher.Minuter = coordClasses, minuterClasses
	}

	return teachers, nil
}

func GetTeachersFast(args map[string]string) (interface{}, error) {
	var teachers []*Teacher

	if err := DB().Order("surname,name").Find(&teachers).Error; err != nil {
		return nil, err
	}
	for _, teacher := range teachers {
		teacher.GetClasses()
	}
	for _, teacher := range teachers {
		teacher.GetSubjects()
	}

	return teachers, nil
}

func SaveTeacher(teacher interface{}) (interface{}, error) {
	if err := DB().Omit("Classes", "Subjects", "Activities").Save(teacher).Error; err != nil {
		return nil, err
	}
	return teacher, nil
}

func UpdateTeacher(args map[string]string, r *http.Request) (IDer, error) {
	teacher, err := GetTeacher(args)
	if err != nil {
		return nil, err
	}

	// FIXME: Should not be hard set.
	teacher.(*Teacher).Regenerate = false

	// FIXME: Should not be hard set.
	teacher.(*Teacher).Exclude = false

	// FIXME: Should not be hard set.
	teacher.(*Teacher).NotInvited = false

	if len(args["DateFrom"]) == 0 {
		// FIXME: Should not be hard set.
		teacher.(*Teacher).DateFrom = time.Time{}
	}

	if len(args["DateTo"]) == 0 {
		// FIXME: Should not be hard set.
		teacher.(*Teacher).DateTo = time.Time{}
	}

	err = renderer.Decode(teacher, r)
	if err != nil {
		return nil, err
	}
	_, err = SaveTeacher(teacher)
	if err != nil {
		return nil, err
	}
	teacher, err = GetTeacher(args)
	if err != nil {
		return nil, err
	}
	return teacher.(*Teacher), nil
}

func AddTeacher(args map[string]string, r *http.Request) (IDer, error) {
	teacher := new(Teacher)
	err := renderer.Decode(teacher, r)
	if err != nil {
		return nil, err
	}
	teacher, err = CreateTeacher(teacher)
	if err != nil {
		return nil, err
	}

	return teacher, nil
}

func CreateTeacher(teacher *Teacher) (*Teacher, error) {
	var teachers []*Teacher
	if err := DB().Where("name=? AND surname=?", teacher.Name, teacher.Surname).Find(&teachers).Error; err != nil {
		return nil, err
	}
	if len(teachers) > 0 {
		return nil, fmt.Errorf("Teacher %s already exists!", teacher.CompleteName())
	}
	if err := DB().Create(teacher).Error; err != nil {
		return nil, err
	}
	return teacher, nil
}

func DeleteTeacher(args map[string]string, r *http.Request) (IDer, error) {
	teacher, err := GetTeacher(args)
	if err != nil {
		return nil, err
	}
	if err := DB().Unscoped().Delete(teacher.(*Teacher)).Error; err != nil {
		return nil, err
	}
	return teacher.(*Teacher), nil
}

func (t *Teacher) GetClasses() ([]*Class, error) {
	if err := DB().Raw(selectUniqueTeacherClasses, t.ID, t.ID).Scan(&t.Classes).Error; err != nil {
		return nil, err
	}
	return t.Classes, nil
}

func (t *Teacher) GetSubjects() ([]*Subject, error) {
	if err := DB().Raw(selectUniqueTeacherSubjects, t.ID, t.ID).Scan(&t.Subjects).Error; err != nil {
		return nil, err
	}
	return t.Subjects, nil
}

func (t *Teacher) GetStudents() ([]*Student, error) {
	if err := DB().Raw(selectUniqueTeacherStudents, t.ID, t.ID).Scan(&t.Students).Error; err != nil {
		return nil, err
	}
	return t.Students, nil
}

func (t *Teacher) GetActivities() ([]*Activity, error) {
	if err := DB().Preload("SupplyTeacher").Preload("Teacher").Preload("Subject").Preload("Class").Preload("Student").Where("teacher_id=? OR supply_teacher_id=?", t.ID, t.ID).Find(&t.Activities).Error; err != nil {
		return nil, err
	}
	return t.Activities, nil
}

func (t *Teacher) GetActivitiesByClass(id string) ([]*Activity, error) {
	if err := DB().Preload("Teacher").Where("class_id=? AND (teacher_id=? or supply_teacher_id=?)", id, t.ID, t.ID).Find(&t.Activities).Error; err != nil {
		return nil, err
	}
	return t.Activities, nil
}

func (t *Teacher) GetSubjectsByClassID(id string) ([]*Subject, error) {
	if err := DB().Raw(selectUniqueTeacherSubjectsByClass, t.ID, t.ID, id).Scan(&t.Subjects).Error; err != nil {
		return nil, err
	}
	return t.Subjects, nil
}

func (t *Teacher) CoordinatorClasses() ([]*Class, error) {
	var output []*Class

	classes, err := t.GetClasses()
	if err != nil {
		return nil, err
	}

	for _, class := range classes {
		if class.CoordinatorID == t.ID {
			output = append(output, class)
		}
	}
	return output, nil
}

func (t *Teacher) MinuterClasses() ([]*Class, error) {
	var output []*Class

	classes, err := t.GetClasses()
	if err != nil {
		return nil, err
	}

	for _, class := range classes {
		if class.MinuterID == t.ID {
			output = append(output, class)
		}
	}
	return output, nil
}

func (t Teacher) IsCoordinatorInClass(className string) bool {
	var classes []Class

	if err := DB().Model(t).Where("name = ?", className).Related(&classes, "Classes").Error; err != nil {
		panic(err)
	}

	for _, class := range classes {
		if class.CoordinatorID == t.ID {
			return true
		}
	}

	return false

}

func (t *Teacher) CompleteName() string {
	return fmt.Sprintf("%s %s", t.Name, t.Surname)
}

func (t *Teacher) RestAPIPath() string {
	return "teachers"
}
