package frontend

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"sort"
	"strconv"

	b "streifling.com/jason/cpolis/cmd/backend"
)

type UserHTMLData struct {
	*b.User
	Title      string
	ButtonText string
	URL        string
	Image      string
}

func checkUserStrings(user *b.User) (string, int, bool) {
	userLen := 63 // max value for utf-8 at 255 bytes
	nameLen := 56 // max value when aes encrypting utf-8 at up to 255 bytes

	if len(user.UserName) > userLen {
		return "Benutzername", userLen, false
	} else if len(user.FirstName) > nameLen {
		return "Vorname", nameLen, false
	} else if len(user.LastName) > nameLen {
		return "Nachname", nameLen, false
	} else {
		return "", 0, true
	}
}

func sortUsersByName(users []*b.User) {
	sort.SliceStable(users, func(i, j int) bool {
		if users[i].LastName == users[j].LastName {
			return users[i].FirstName < users[j].FirstName
		}
		return users[i].LastName < users[j].LastName
	})
}

func CreateUser(c *b.Config, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if _, err := ManageSession(w, r, c, s); err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		data := &UserHTMLData{
			User:       &b.User{Role: b.Author},
			Title:      "Neuer Benutzer",
			ButtonText: "Anlegen",
			URL:        "/user/add",
		}

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "edit-user.html"))
		if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func AddUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ManageSession(w, r, c, s)
		if err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		user := &b.User{
			UserName:       r.PostFormValue("username"),
			FirstName:      r.PostFormValue("first-name"),
			LastName:       r.PostFormValue("last-name"),
			Email:          r.PostFormValue("email"),
			ProfilePicLink: r.PostFormValue("profile-pic-url"),
		}
		pass := r.PostFormValue("password")
		pass2 := r.PostFormValue("password2")
		email2 := r.PostFormValue("email2")

		if len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || len(user.Email) == 0 || len(email2) == 0 || len(pass) == 0 || len(pass2) == 0 {
			http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest)
			return
		}

		userString, stringLen, ok := checkUserStrings(user)
		if !ok {
			http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest)
			return
		}

		if id := db.GetID(user.UserName); id != 0 {
			http.Error(w, user.UserName+" ist bereits vergeben. Bitte anderen Benutzernamen wählen.", http.StatusBadRequest)
			return
		}

		if user.Email != email2 {
			http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
			return
		}

		if pass != pass2 {
			http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
			return
		}

		roleString := r.PostFormValue("role")
		if len(roleString) == 0 {
			http.Error(w, "Bitte eine Aufgabe vergeben.", http.StatusBadRequest)
			return
		}

		user.Role, err = strconv.Atoi(roleString)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		_, err = db.AddUser(c, user, pass)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data := new(struct{ Role int })
		data.Role = session.User.Role

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "hub.html"))
		tmpl = template.Must(tmpl, err)
		if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func EditSelf(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ManageSession(w, r, c, s)
		if err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		user, err := db.GetUser(c, session.User.ID)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data := &UserHTMLData{
			User:       user,
			Title:      "Mein Profil bearbeiten",
			ButtonText: "Übernehmen",
			URL:        "/user/update/self",
			Image:      user.ProfilePicLink,
		}

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "edit-user.html"))
		if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func UpdateSelf(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ManageSession(w, r, c, s)
		if err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		user := &b.User{
			ID:             session.User.ID,
			UserName:       r.PostFormValue("username"),
			FirstName:      r.PostFormValue("first-name"),
			LastName:       r.PostFormValue("last-name"),
			Email:          r.PostFormValue("email"),
			ProfilePicLink: r.PostFormValue("profile-pic-url"),
		}

		oldPass := r.PostFormValue("old-password")
		newPass := r.PostFormValue("password")
		newPass2 := r.PostFormValue("password2")
		email2 := r.PostFormValue("email2")

		if len(user.UserName) == 0 {
			http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest)
			return
		}

		if len(user.FirstName) == 0 || len(user.LastName) == 0 {
			http.Error(w, "Bitte den vollständigen Namen ausfüllen.", http.StatusBadRequest)
			return
		}

		if len(user.Email) == 0 || len(email2) == 0 {
			http.Error(w, "Bitte die Emailadresse ausfüllen.", http.StatusBadRequest)
			return
		}
		if user.Email != email2 {
			http.Error(w, "Die Emailadressen stimmen nicht überein", http.StatusBadRequest)
			return
		}

		if newPass != newPass2 {
			http.Error(w, "Die Passwörter stimmen nicht überein", http.StatusBadRequest)
			return
		}

		userString, stringLen, ok := checkUserStrings(user)
		if !ok {
			http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest)
			return
		}

		if id := db.GetID(user.UserName); id != 0 && id != user.ID {
			http.Error(w, user.UserName+" ist bereits vergeben. Bitte anderen Benutzernamen wählen.", http.StatusBadRequest)
			return
		}

		if err = db.UpdateOwnUserAttributes(c, user.ID, user.UserName, user.FirstName, user.LastName, user.Email, user.ProfilePicLink, oldPass, newPass); err != nil {
			log.Println("error: user:", user.ID, err)
			http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError)
			return
		}

		data := new(struct{ Role int })
		data.Role = session.User.Role

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "hub.html"))
		tmpl = template.Must(tmpl, err)
		if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func AddFirstUser(c *b.Config, db *b.DB, s map[string]*Session, sessionExpiryChan chan string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var err error
		user := &b.User{
			UserName:       r.PostFormValue("username"),
			FirstName:      r.PostFormValue("first-name"),
			LastName:       r.PostFormValue("last-name"),
			Email:          r.PostFormValue("email"),
			ProfilePicLink: r.PostFormValue("profile-pic-url"),
			Role:           b.Admin,
		}
		pass := r.PostFormValue("password")
		pass2 := r.PostFormValue("password2")
		email2 := r.PostFormValue("email2")

		if len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || len(user.Email) == 0 || len(email2) == 0 || len(pass) == 0 || len(pass2) == 0 {
			http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest)
			return
		}

		userString, stringLen, ok := checkUserStrings(user)
		if !ok {
			http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest)
			return
		}

		if user.Email != email2 {
			http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
			return
		}

		if pass != pass2 {
			http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
			return
		}

		user.ID, err = db.AddFirstUser(c, user, pass)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if user.ID == -1 {
			http.Error(w, "Bitte ein Benutzerkonto von einem Administrator anlegen lassen.", http.StatusInternalServerError)
			return
		}

		if _, err := db.AddIssue(); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		session := newSession(w, c, sessionExpiryChan, user)
		s[session.cookie.Value] = session
		http.SetCookie(w, session.cookie)

		data := new(struct{ Role int })
		data.Role = user.Role

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "hub.html"))
		if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func ShowAllUsers(c *b.Config, db *b.DB, s map[string]*Session, action string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ManageSession(w, r, c, s)
		if err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		data := new(struct {
			Users  map[int64]*b.User
			Action string
		})

		data.Action = action
		data.Users, err = db.GetAllUsersMap(c)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		delete(data.Users, session.User.ID)

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "show-all-users.html"))
		if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func EditUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if _, err := ManageSession(w, r, c, s); err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		user, err := db.GetUser(c, id)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data := &UserHTMLData{
			User:       user,
			Title:      "Profil von " + user.FirstName + " " + user.LastName + " bearbeiten",
			ButtonText: "Übernehmen",
			URL:        fmt.Sprint("/user/update/", user.ID),
			Image:      user.ProfilePicLink,
		}

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "edit-user.html"))
		if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func UpdateUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ManageSession(w, r, c, s)
		if err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		user := new(b.User)
		user.ID, err = strconv.ParseInt(r.PathValue("id"), 10, 64)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		user.Role, err = strconv.Atoi(r.PostFormValue("role"))
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		user.UserName = r.PostFormValue("username")
		if len(user.UserName) == 0 {
			http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest)
			return
		}

		user.FirstName = r.PostFormValue("first-name")
		user.LastName = r.PostFormValue("last-name")
		if len(user.FirstName) == 0 || len(user.LastName) == 0 {
			http.Error(w, "Bitte den vollständigen Namen ausfüllen.", http.StatusBadRequest)
			return
		}

		user.Email = r.PostFormValue("email")
		email2 := r.PostFormValue("email2")
		if len(user.Email) == 0 || len(email2) == 0 {
			http.Error(w, "Bitte die Emailadresse ausfüllen.", http.StatusBadRequest)
			return
		}
		if user.Email != email2 {
			http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
			return
		}

		user.ProfilePicLink = r.PostFormValue("profile-pic-url")

		newPass := r.PostFormValue("password")
		newPass2 := r.PostFormValue("password2")
		if newPass != newPass2 {
			http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
			return
		}

		userString, stringLen, ok := checkUserStrings(user)
		if !ok {
			http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest)
			return
		}

		if id := db.GetID(user.UserName); id != 0 && id != user.ID {
			http.Error(w, user.UserName+" ist bereits vergeben. Bitte anderen Benutzernamen wählen.", http.StatusBadRequest)
			return
		}

		if err = db.UpdateUserAttributes(c, user.ID, user.UserName, user.FirstName, user.LastName, user.Email, user.ProfilePicLink, newPass, user.Role); err != nil {
			log.Println("error: user:", user.ID, err)
			http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError)
			return
		}

		data := new(struct{ Role int })
		data.Role = session.User.Role

		tmpl := template.Must(template.ParseFiles(filepath.Join(c.WebDir, "templates", "hub.html")))
		if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func DeleteUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ManageSession(w, r, c, s)
		if err != nil {
			http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
			return
		}

		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		if err = db.DeleteUser(id); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data := new(struct{ Role int })
		data.Role = session.User.Role

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "hub.html"))
		tmpl = template.Must(tmpl, err)
		if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}