package controllers

import (
	"fmt"
	"log"
	"net/http"
	"net/mail"
	"officeHours/config"
	"officeHours/models"
	"officeHours/templating"
	"strconv"
	"strings"
)

type maskData struct {
	Courses           []models.Course
	Rooms             []models.Room
	MinuteGranularity int
	MaxDuration       int
	EarliestStartTime struct {
		Hour   int
		Minute int
	}
	LatestStartTime struct {
		Hour   int
		Minute int
	}
	SelectedCourse         int
	SelectedRoom           int
	Date                   models.Date
	Duration               int
	Roomname               string
	Name                   string
	Email                  string
	SubscribeToMailinglist bool
	Info                   string
	Errors                 []string
	Config                 config.Config
}

// Offer a form to add office hours and validate its input on receiving.
func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Request) {
	SendSecurityHeaders(w, req)
	var errors []string
	courses, err := b.courseRepo.GetAll()
	if err != nil {
		errors = append(errors, err.Error())
	}
	rooms, err := b.roomRepo.GetAll()
	if err != nil {
		errors = append(errors, err.Error())
	}
	//Parse course
	courseid, err := strconv.Atoi(req.FormValue("veranstaltung"))
	if err != nil {
		errors = append(errors, "Die Veranstaltung muss eine ganze Zahl sein.")
	}
	course, err := b.courseRepo.FindById(courseid)
	if err != nil {
		errors = append(errors, "Die Veranstaltung muss existieren.")
	}
	//Parse room
	roomid, err := strconv.Atoi(req.FormValue("raum"))
	if err != nil {
		errors = append(errors, "Der Raum muss eine ganze Zahl sein.")
	}
	room, err := b.roomRepo.FindById(roomid)
	if err != nil {
		errors = append(errors, "Der Raum muss existieren.")
	}
	//Parse date
	week, err := strconv.Atoi(req.FormValue("woche"))
	if err != nil {
		errors = append(errors, "Die Vorlesungswoche muss eine ganze Zahl sein.")
	}
	if !(week >= 0 && week <= 2) {
		errors = append(errors, "Sprechstunden müssen jede, jede gerade oder jede ungerade Vorlesungswoche stattfinden.")
	}
	day, err := strconv.Atoi(req.FormValue("tag"))
	if err != nil {
		errors = append(errors, "Der Tag muss eine ganze Zahl sein.")
	}
	if !(day >= 0 && day <= 4) {
		errors = append(errors, "Sprechstunden müssen von Montag bis Freitag stattfinden.")
	}
	time := strings.SplitN(req.FormValue("startzeit"), ":", 2)
	var hour, minute int
	if len(time) != 2 {
		errors = append(errors, "Die Zeit muss im Format HH:MM sein.")
	} else {
		hour, err = strconv.Atoi(time[0])
		if err != nil {
			errors = append(errors, "Die Stunde muss eine ganze Zahl sein.")
		}
		if !(hour > b.config.Date.EarliestStartTime.Hour || (hour == b.config.Date.EarliestStartTime.Hour && minute >= b.config.Date.EarliestStartTime.Minute)) {
			errors = append(errors, fmt.Sprintf("Sprechstunden müssen nach %02d:%02d Uhr starten.", b.config.Date.EarliestStartTime.Hour, b.config.Date.EarliestStartTime.Minute))
		}
		if !(hour < b.config.Date.LatestStartTime.Hour || (hour == b.config.Date.LatestStartTime.Hour && minute <= b.config.Date.LatestStartTime.Minute)) {
			errors = append(errors, fmt.Sprintf("Sprechstunden müssen vor %02d:%02d Uhr starten.", b.config.Date.LatestStartTime.Hour, b.config.Date.LatestStartTime.Minute))
		}
		minute, err = strconv.Atoi(time[1])
		if err != nil {
			errors = append(errors, "Die Minute muss eine ganze Zahl sein.")
		}
		if !(minute >= 0 && minute <= 60-b.config.Date.MinuteGranularity && minute%b.config.Date.MinuteGranularity == 0) {
			errors = append(errors, fmt.Sprintf("Sprechstunden dürfen nur alle %d Minuten starten.", b.config.Date.MinuteGranularity))
		}
	}
	date := models.Date{Week: week, Day: day, Hour: hour, Minute: minute}
	duration, err := strconv.Atoi(req.FormValue("dauer"))
	if err != nil {
		errors = append(errors, "Die Dauer muss eine ganze Zahl sein.")
	}
	if !(duration >= b.config.Date.MinuteGranularity && duration <= b.config.Date.MaxDuration && duration%b.config.Date.MinuteGranularity == 0) {
		errors = append(errors, fmt.Sprintf("Sprechstunden müssen zwischen %d und %d Minuten lang sein.", b.config.Date.MinuteGranularity, b.config.Date.MaxDuration))
	}

	roomname := req.FormValue("raumname")
	if roomname != "" && room.Name != "Sonstige" {
		errors = append(errors, "Sonderraumnamen können nur für den Raum \"Sonstige\" angegeben werden.")
	}
	name := req.FormValue("name")
	if name == "" {
		errors = append(errors, "Der Name darf nicht leer sein.")
	}
	email, err := mail.ParseAddress(req.FormValue("email"))
	if err != nil {
		email = &mail.Address{Name: "", Address: req.FormValue("email")}
		errors = append(errors, "Mailaddresse konnte nicht geparst werden.")
	} else if !(b.config.Tutor.MailSuffix == "" || strings.HasSuffix(email.Address, "@"+b.config.Tutor.MailSuffix) || strings.HasSuffix(email.Address, "."+b.config.Tutor.MailSuffix)) {
		errors = append(errors, fmt.Sprintf("Mailaddresse muss auf „%s“ enden.", b.config.Tutor.MailSuffix))
	}

	subscribeToMailinglist := req.FormValue("subscribeToMailinglist") == "subscribe"

	info := req.FormValue("info")

	allowed, err := b.officeHourRepo.AllowedAt(date, duration, room, true)
	if err != nil {
		errors = append(errors, fmt.Sprintf("Fehler beim Abfragen der Raumkapazität: %s", err.Error()))
	} else if !allowed {
		errors = append(errors, "In dem Raum muss noch Platz für weitere Sprechstunden sein.")
	}

	// If there were errors in the data for the new office hour,
	// answer with the form prefilled with the sent data.
	if len(errors) != 0 {
		var data maskData = maskData{
			courses,
			rooms,
			b.config.Date.MinuteGranularity,
			b.config.Date.MaxDuration,
			b.config.Date.EarliestStartTime,
			b.config.Date.LatestStartTime,
			courseid,
			roomid,
			date,
			duration,
			roomname,
			name,
			email.Address,
			subscribeToMailinglist,
			info,
			errors,
			b.config,
		}
		b.writeAddOfficeHourMask(w, req, data)
	} else {
		// if the data for a new office hour was sent correctly, save it.

		officeHour := models.OfficeHour{Id: 0,
			Tutor:    models.Tutor{Id: 0, Name: name, Email: email.Address, SubscribeToMailinglist: subscribeToMailinglist},
			Date:     date,
			Room:     room,
			RoomName: roomname,
			Course:   course,
			Info:     info,
			Active:   false,
			Duration: duration,
		}
		id, err := b.officeHourRepo.Add(officeHour)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			templating.ServeTemplate(w, "addFailure", err)
			return
		}
		officeHour, err = b.officeHourRepo.FindById(id)
		if err != nil {
			log.Printf("Error finding new office hour by id %d: %s", id, err.Error())
		}
		_, err = b.requestRepo.Add(officeHour, models.RequestActivate)
		if err != nil {
			log.Printf("Error adding request: %s", err.Error())
		}
		templating.ServeTemplate(w, "addSuccess", nil)
	}
}

func (b *BaseHandler) writeAddOfficeHourMask(w http.ResponseWriter, req *http.Request, data maskData) {
	if req.Method == http.MethodGet { // if the current request is GET, we assume no office hour addition was tried so far and reset the errors.
		data.Errors = []string{}
	}
	if len(data.Errors) != 0 {
		w.WriteHeader(http.StatusBadRequest)
	}
	templating.ServeTemplate(w, "addMask", data)
}
