package index

import (
	"fmt"
	"html/template"
	"net/http"
	"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/markdown"
	"code.nonshy.com/nonshy/website/pkg/models"
	"code.nonshy.com/nonshy/website/pkg/ratelimit"
	"code.nonshy.com/nonshy/website/pkg/session"
	"code.nonshy.com/nonshy/website/pkg/templates"
)

// Contact or report a problem.
func Contact() http.HandlerFunc {
	tmpl := templates.Must("contact.html")
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Query and form POST parameters.
		var (
			intent          = r.FormValue("intent")
			subject         = r.FormValue("subject")
			title           = "Contact Us"
			message         = r.FormValue("message")
			replyTo         = r.FormValue("email")
			trap1           = r.FormValue("url") != "https://"
			trap2           = r.FormValue("comment") != ""
			tableID         int
			tableName       string
			tableLabel      string // front-end user feedback about selected report item
			messageRequired = true // unless we have a table ID to work with
			success         = "Thank you for your feedback! Your message has been delivered to the website administrators."
		)

		// For report intents: ID of the user, photo, message, etc.
		tableID, err := strconv.Atoi(r.FormValue("id"))
		if err != nil {
			// The tableID is not an int - was it a username?
			if user, err := models.FindUser(r.FormValue("id")); err == nil {
				tableID = int(user.ID)
			}
		}
		if tableID > 0 {
			messageRequired = false
		}

		// In what context is the ID given?
		if subject != "" && tableID > 0 {
			switch subject {
			case "report.user":
				tableName = "users"
				if user, err := models.GetUser(uint64(tableID)); err == nil {
					tableLabel = fmt.Sprintf(`User account "%s"`, user.Username)
				} else {
					log.Error("/contact: couldn't produce table label for user %d: %s", tableID, err)
				}
			case "report.photo":
				tableName = "photos"

				// Find this photo and the user associated.
				if pic, err := models.GetPhoto(uint64(tableID)); err == nil {
					if user, err := models.GetUser(pic.UserID); err == nil {
						tableLabel = fmt.Sprintf(`A profile photo of user account "%s"`, user.Username)
					} else {
						log.Error("/contact: couldn't produce table label for user %d: %s", tableID, err)
					}
				} else {
					log.Error("/contact: couldn't produce table label for photo %d: %s", tableID, err)
				}
			case "report.message":
				tableName = "messages"
				tableLabel = "Direct Message conversation"
			case "report.comment":
				tableName = "comments"

				// Find this comment.
				if comment, err := models.GetComment(uint64(tableID)); err == nil {
					tableLabel = fmt.Sprintf(`A comment written by "%s"`, comment.User.Username)
				} else {
					log.Error("/contact: couldn't produce table label for comment %d: %s", tableID, err)
				}
			}
		}

		// On POST: take what we have now and email the admins.
		if r.Method == http.MethodPost {
			// Look up the current user, in case logged in.
			currentUser, err := session.CurrentUser(r)
			if err == nil {
				replyTo = currentUser.Email
			}

			// We were getting too much spam logged-out: prevent logged-out bots from still posting.
			if currentUser == nil {
				log.Error("Blocked POST /contact because user is logged-out")
				session.FlashError(w, r, "Our contact form is only for logged-in users, sorry!")
				templates.Redirect(w, "/contact")
				return
			}

			// Rate limit submissions, especially for logged-out users.
			if currentUser == nil {
				limiter := &ratelimit.Limiter{
					Namespace:  "contact",
					ID:         session.RemoteAddr(r),
					Limit:      config.ContactRateLimit,
					Window:     config.ContactRateLimitWindow,
					CooldownAt: config.ContactRateLimitCooldownAt,
					Cooldown:   config.ContactRateLimitCooldown,
				}

				if err := limiter.Ping(); err != nil {
					session.FlashError(w, r, err.Error())
					templates.Redirect(w, r.URL.Path)
					return
				}
			}

			// If they have tripped the spam bot trap fields, don't save their message.
			if trap1 || trap2 {
				log.Error("Contact form: bot has tripped the trap fields, do not save message")
				session.Flash(w, r, success)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Store feedback in the database.
			fb := &models.Feedback{
				Intent:    intent,
				Subject:   subject,
				Message:   message,
				TableName: tableName,
				TableID:   uint64(tableID),
			}

			if currentUser != nil && currentUser.ID > 0 {
				fb.UserID = currentUser.ID
			} else if replyTo != "" {
				fb.ReplyTo = replyTo
			}

			if err := models.CreateFeedback(fb); err != nil {
				session.FlashError(w, r, "Couldn't save feedback: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Email the admins.
			if err := mail.Send(mail.Message{
				To:       config.Current.AdminEmail,
				Subject:  "User Feedback: " + title,
				Template: "email/contact_admin.html",
				Data: map[string]interface{}{
					"Title":       title,
					"Intent":      intent,
					"Subject":     subject,
					"Message":     template.HTML(markdown.Render(message)),
					"TableName":   tableName,
					"TableID":     tableID,
					"CurrentUser": currentUser,
					"ReplyTo":     replyTo,
					"BaseURL":     config.Current.BaseURL,
					"AdminURL":    config.Current.BaseURL + "/admin/feedback",
				},
			}); err != nil {
				log.Error("/contact page: couldn't send email: %s", err)
			}

			session.Flash(w, r, success)
			templates.Redirect(w, r.URL.Path)
			return
		}

		// Default intent = contact
		if intent == "report" {
			title = "Report a Problem"
		} else {
			intent = "contact"
		}

		// Validate the subject.
		if subject != "" {
			var found bool
			for _, group := range config.ContactUsChoices {
				for _, opt := range group.Options {
					if opt.Value == subject {
						found = true
						break
					}
				}
			}

			if !found {
				subject = ""
			}
		}

		var vars = map[string]interface{}{
			"Intent":          intent,
			"TableID":         r.FormValue("id"),
			"TableLabel":      tableLabel,
			"Subject":         subject,
			"PageTitle":       title,
			"Subjects":        config.ContactUsChoices,
			"Message":         message,
			"MessageRequired": messageRequired,
		}
		if err := tmpl.Execute(w, r, vars); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}
