package photo

import (
	"bytes"
	"io"
	"net/http"
	"path/filepath"
	"strconv"

	"code.nonshy.com/nonshy/website/pkg/config"
	"code.nonshy.com/nonshy/website/pkg/log"
	"code.nonshy.com/nonshy/website/pkg/mail"
	"code.nonshy.com/nonshy/website/pkg/models"
	"code.nonshy.com/nonshy/website/pkg/photo"
	"code.nonshy.com/nonshy/website/pkg/session"
	"code.nonshy.com/nonshy/website/pkg/templates"
)

// CertificationRequiredError handles the error page when a user is denied due to lack of certification.
func CertificationRequiredError() http.HandlerFunc {
	tmpl := templates.Must("errors/certification_required.html")
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			session.FlashError(w, r, "Unexpected error: could not get currentUser.")
			templates.Redirect(w, "/")
			return
		}

		// Get the current user's cert photo (or create the DB record).
		cert, err := models.GetCertificationPhoto(currentUser.ID)
		if err != nil {
			session.FlashError(w, r, "Unexpected error: could not get or create CertificationPhoto record.")
			templates.Redirect(w, "/")
			return
		}

		var vars = map[string]interface{}{
			"CertificationPhoto": cert,
		}
		if err := tmpl.Execute(w, r, vars); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}

// Certification photo controller.
func Certification() http.HandlerFunc {
	tmpl := templates.Must("photo/certification.html")
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			session.FlashError(w, r, "Unexpected error: could not get currentUser.")
			templates.Redirect(w, "/")
			return
		}

		// Get the current user's cert photo (or create the DB record).
		cert, err := models.GetCertificationPhoto(currentUser.ID)
		if err != nil {
			session.FlashError(w, r, "Unexpected error: could not get or create CertificationPhoto record.")
			templates.Redirect(w, "/")
			return
		}

		// Uploading?
		if r.Method == http.MethodPost {
			// Are they deleting their photo?
			if r.PostFormValue("delete") == "true" {
				if cert.Filename != "" {
					if err := photo.Delete(cert.Filename); err != nil {
						log.Error("Failed to delete old cert photo for %s (%s): %s", currentUser.Username, cert.Filename, err)
					}
					cert.Filename = ""
				}

				cert.Status = models.CertificationPhotoNeeded
				cert.AdminComment = ""
				cert.Save()

				// Removing your photo = not certified again.
				currentUser.Certified = false
				if err := currentUser.Save(); err != nil {
					session.FlashError(w, r, "Error saving your User data: %s", err)
				}

				session.Flash(w, r, "Your certification photo has been deleted.")
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Get the uploaded file.
			file, header, err := r.FormFile("file")
			if err != nil {
				session.FlashError(w, r, "Error receiving your file: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			var buf bytes.Buffer
			io.Copy(&buf, file)

			filename, _, err := photo.UploadPhoto(photo.UploadConfig{
				User:      currentUser,
				Extension: filepath.Ext(header.Filename),
				Data:      buf.Bytes(),
			})
			if err != nil {
				session.FlashError(w, r, "Error processing your upload: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Are they replacing their old photo?
			if cert.Filename != "" {
				if err := photo.Delete(cert.Filename); err != nil {
					log.Error("Failed to delete old cert photo for %s (%s): %s", currentUser.Username, cert.Filename, err)
				}
			}

			// Update their certification photo.
			cert.Status = models.CertificationPhotoPending
			cert.Filename = filename
			cert.AdminComment = ""
			if err := cert.Save(); err != nil {
				session.FlashError(w, r, "Error saving your CertificationPhoto: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Set their approval status back to false.
			currentUser.Certified = false
			if err := currentUser.Save(); err != nil {
				session.FlashError(w, r, "Error saving your User data: %s", err)
			}

			// Notify the admin email to check out this photo.
			if err := mail.Send(mail.Message{
				To:       config.Current.AdminEmail,
				Subject:  "New Certification Photo Needs Approval",
				Template: "email/certification_admin.html",
				Data: map[string]interface{}{
					"User": currentUser,
					"URL":  config.Current.BaseURL + "/admin/photo/certification",
				},
			}); err != nil {
				log.Error("Certification: failed to notify admins of pending photo: %s", err)
			}

			session.Flash(w, r, "Your certification photo has been uploaded and is now awaiting approval.")
			templates.Redirect(w, r.URL.Path)
			return
		}

		var vars = map[string]interface{}{
			"CertificationPhoto": cert,
		}

		if err := tmpl.Execute(w, r, vars); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}

// AdminCertification controller (/admin/photo/certification)
func AdminCertification() http.HandlerFunc {
	tmpl := templates.Must("admin/certification.html")
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// View status
		var view = r.FormValue("view")
		if view == "" {
			view = "pending"
		}

		// Get the current user.
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			session.FlashError(w, r, "Couldn't get CurrentUser: %s", err)
		}

		// Scope check based on view.
		switch view {
		case "pending":
			// Scope check.
			if !currentUser.HasAdminScope(config.ScopeCertificationApprove) {
				session.FlashError(w, r, "Missing admin scope: %s", config.ScopeCertificationApprove)
				templates.Redirect(w, "/admin")
				return
			}
		case "approved", "rejected":
			// Scope check.
			if !currentUser.HasAdminScope(config.ScopeCertificationList) {
				session.FlashError(w, r, "Missing admin scope: %s", config.ScopeCertificationList)
				templates.Redirect(w, "/admin")
				return
			}
		}

		// Short circuit the GET view for username/email search (exact match)
		if username := r.FormValue("username"); username != "" {
			// Scope check.
			if !currentUser.HasAdminScope(config.ScopeCertificationView) {
				session.FlashError(w, r, "Missing admin scope: %s", config.ScopeCertificationView)
				templates.Redirect(w, "/admin")
				return
			}

			user, err := models.FindUser(username)
			if err != nil {
				session.FlashError(w, r, "Username or email '%s' not found.", username)
				templates.Redirect(w, r.URL.Path)
				return
			}

			cert, err := models.GetCertificationPhoto(user.ID)
			if err != nil {
				session.FlashError(w, r, "Couldn't get their certification photo: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			var vars = map[string]interface{}{
				"View":      view,
				"Photos":    []*models.CertificationPhoto{cert},
				"UserMap":   &models.UserMap{user.ID: user},
				"FoundUser": user,
			}
			if err := tmpl.Execute(w, r, vars); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			return
		}

		// Making a verdict?
		if r.Method == http.MethodPost {
			// Scope check.
			if !currentUser.HasAdminScope(config.ScopeCertificationApprove) {
				session.FlashError(w, r, "Missing admin scope: %s", config.ScopeCertificationApprove)
				templates.Redirect(w, r.URL.Path)
				return
			}

			var (
				comment = r.PostFormValue("comment")
				verdict = r.PostFormValue("verdict")
			)

			userID, err := strconv.Atoi(r.PostFormValue("user_id"))
			if err != nil {
				session.FlashError(w, r, "Invalid user_id data type.")
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Look up the user in case we'll toggle their Certified state.
			user, err := models.GetUser(uint64(userID))
			if err != nil {
				session.FlashError(w, r, "Couldn't get user ID %d: %s", userID, err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Look up this photo.
			cert, err := models.GetCertificationPhoto(uint64(userID))
			if err != nil {
				session.FlashError(w, r, "Couldn't get certification photo.")
				templates.Redirect(w, r.URL.Path)
				return
			} else if cert.Filename == "" {
				session.FlashError(w, r, "That photo has no filename anymore??")
				templates.Redirect(w, r.URL.Path)
				return
			}

			switch verdict {
			case "reject":
				if comment == "" {
					session.FlashError(w, r, "An admin comment is required when rejecting a photo.")
				} else {
					cert.Status = models.CertificationPhotoRejected
					cert.AdminComment = comment
					if err := cert.Save(); err != nil {
						session.FlashError(w, r, "Failed to save CertificationPhoto: %s", err)
						templates.Redirect(w, r.URL.Path)
						return
					}

					// Uncertify the user just in case.
					user.Certified = false
					user.Save()

					// Notify the user about this rejection.
					notif := &models.Notification{
						UserID:    user.ID,
						AboutUser: *user,
						Type:      models.NotificationCertRejected,
						Message:   comment,
					}
					if err := models.CreateNotification(notif); err != nil {
						log.Error("Couldn't create rejection notification: %s", err)
					}

					// Notify the user via email.
					if err := mail.Send(mail.Message{
						To:       user.Email,
						Subject:  "Your certification photo has been rejected",
						Template: "email/certification_rejected.html",
						Data: map[string]interface{}{
							"Username":     user.Username,
							"AdminComment": comment,
							"URL":          config.Current.BaseURL + "/photo/certification",
						},
					}); err != nil {
						session.FlashError(w, r, "Note: failed to email user about the rejection: %s", err)
					}
				}

				session.Flash(w, r, "Certification photo rejected!")
			case "approve":
				cert.Status = models.CertificationPhotoApproved
				cert.AdminComment = ""
				if err := cert.Save(); err != nil {
					session.FlashError(w, r, "Failed to save CertificationPhoto: %s", err)
					templates.Redirect(w, r.URL.Path)
					return
				}

				// Certify the user!
				user.Certified = true
				user.Save()

				// Notify the user about this approval.
				notif := &models.Notification{
					UserID:    user.ID,
					AboutUser: *user,
					Type:      models.NotificationCertApproved,
				}
				if err := models.CreateNotification(notif); err != nil {
					log.Error("Couldn't create approval notification: %s", err)
				}

				// Notify the user via email.
				if err := mail.Send(mail.Message{
					To:       user.Email,
					Subject:  "Your certification photo has been approved!",
					Template: "email/certification_approved.html",
					Data: map[string]interface{}{
						"Username": user.Username,
						"URL":      config.Current.BaseURL,
					},
				}); err != nil {
					session.FlashError(w, r, "Note: failed to email user about the approval: %s", err)
				}

				session.Flash(w, r, "Certification photo approved!")
			default:
				session.FlashError(w, r, "Unsupported verdict option: %s", verdict)
			}
			templates.Redirect(w, r.URL.Path)
			return
		}

		// Get the pending photos.
		pager := &models.Pagination{
			Page:    1,
			PerPage: config.PageSizeAdminCertification,
			Sort:    "updated_at desc",
		}
		pager.ParsePage(r)
		photos, err := models.CertificationPhotosNeedingApproval(models.CertificationPhotoStatus(view), pager)
		if err != nil {
			session.FlashError(w, r, "Couldn't load certification photos from DB: %s", err)
		}

		// Map user IDs.
		var userIDs = []uint64{}
		for _, p := range photos {
			userIDs = append(userIDs, p.UserID)
		}
		userMap, err := models.MapUsers(currentUser, userIDs)
		if err != nil {
			session.FlashError(w, r, "Couldn't map user IDs: %s", err)
		}

		var vars = map[string]interface{}{
			"View":    view,
			"Photos":  photos,
			"UserMap": userMap,
			"Pager":   pager,
		}
		if err := tmpl.Execute(w, r, vars); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}
