package barertc

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

	"code.nonshy.com/nonshy/website/pkg/config"
	"code.nonshy.com/nonshy/website/pkg/controller/api"
	"code.nonshy.com/nonshy/website/pkg/log"
	"code.nonshy.com/nonshy/website/pkg/models"
	"code.nonshy.com/nonshy/website/pkg/templates"
	"code.nonshy.com/nonshy/website/pkg/utility"
)

// WebhookRequest is a JSON request wrapper around all webhook messages.
type WebhookRequest struct {
	Action string
	APIKey string

	// Relevant body per request.
	Report WebhookRequestReport `json:",omitempty"`

	// Optional params per request.
	Username string `json:",omitempty"` // e.g. for profile webhook
}

// WebhookRequestReport is the body for 'report' webhook messages.
type WebhookRequestReport struct {
	FromUsername  string
	AboutUsername string
	Channel       string
	Timestamp     string
	Reason        string
	Message       string
	Comment       string
}

// Report webhook controller.
func Report() http.HandlerFunc {

	// Response JSON schema.
	type Response struct {
		OK    bool
		Error string `json:",omitempty"`
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			api.SendJSON(w, http.StatusNotAcceptable, Response{
				Error: "POST method only",
			})
			return
		}

		// Parse request payload.
		var req WebhookRequest
		if err := api.ParseJSON(r, &req); err != nil {
			api.SendJSON(w, http.StatusBadRequest, Response{
				Error: fmt.Sprintf("Error with request payload: %s", err),
			})
			return
		}

		// Validate the AdminAPIKey.
		if req.APIKey != config.Current.CronAPIKey {
			api.SendJSON(w, http.StatusForbidden, Response{
				Error: "Invalid API Key",
			})
			return
		}

		// Get the report out.
		report := req.Report
		if report.Comment == "" {
			report.Comment = "(no comment)"
		}

		log.Debug("Got chat report: %+v", report)

		// Create an admin Feedback model.
		fb := &models.Feedback{
			Intent:  "report",
			Subject: "report.chat",
			Message: fmt.Sprintf(
				"A message was reported on the chat room!\n\n"+
					"* From username: [%s](/u/%s)\n"+
					"* About username: [%s](/u/%s)\n"+
					"* Channel: **%s**\n"+
					"* Timestamp: %s\n"+
					"* Classification: %s\n"+
					"* User comment: %s\n\n"+
					"- - - - -\n\n"+
					"The reported message on chat was:\n\n%s",
				report.FromUsername, report.FromUsername,
				report.AboutUsername, report.AboutUsername,
				report.Channel,
				report.Timestamp,
				report.Reason,
				report.Comment,
				report.Message,
			),
		}

		// Get the Reply-To user if possible.
		currentUser, err := models.FindUser(report.FromUsername)
		if err == nil {
			fb.UserID = currentUser.ID
		} else {
			currentUser = nil
		}

		// Look up the AboutUser ID if possible.
		targetUser, err := models.FindUser(report.AboutUsername)
		if err == nil {
			fb.TableName = "users"
			fb.TableID = targetUser.ID
		} else {
			log.Error("BareRTC Chat Feedback: couldn't find user ID for AboutUsername=%s: %s", report.AboutUsername, err)
		}

		// Save the feedback.
		if err := models.CreateFeedback(fb); err != nil {
			log.Error("Couldn't save feedback from BareRTC report endpoint: %s", err)
		}

		// Send success response.
		api.SendJSON(w, http.StatusOK, Response{
			OK: true,
		})
	})
}

// Profile webhook controller to fetch more profile details for a user.
func Profile() http.HandlerFunc {

	// Response JSON schema.
	type ProfileField struct {
		Name  string
		Value string
	}
	type Response struct {
		OK            bool
		Error         string `json:",omitempty"`
		ProfileFields []ProfileField
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			api.SendJSON(w, http.StatusNotAcceptable, Response{
				Error: "POST method only",
			})
			return
		}

		// Parse request payload.
		var req WebhookRequest
		if err := api.ParseJSON(r, &req); err != nil {
			api.SendJSON(w, http.StatusBadRequest, Response{
				Error: fmt.Sprintf("Error with request payload: %s", err),
			})
			return
		}

		// Validate the AdminAPIKey.
		if req.APIKey != config.Current.CronAPIKey {
			api.SendJSON(w, http.StatusForbidden, Response{
				Error: "Invalid API Key",
			})
			return
		}

		// Get the Reply-To user if possible.
		currentUser, err := models.FindUser(req.Username)
		if err != nil {
			api.SendJSON(w, http.StatusForbidden, Response{
				Error: "Username Not Found",
			})
			return
		}

		// Populate their profile fields.
		var maritalStatus = currentUser.GetProfileFieldOr("marital_status", "n/a")
		if relationshipType := currentUser.GetProfileField("relationship_type"); relationshipType != "" {
			maritalStatus += fmt.Sprintf(" (%s)", relationshipType)
		}

		var gender = currentUser.GetProfileFieldOr("gender", "n/a")
		if pronouns := currentUser.GetProfileField("pronouns"); pronouns != "" {
			gender += fmt.Sprintf(" (%s)", pronouns)
		}

		var photoCount = models.CountPublicPhotos(currentUser.ID)

		// Member Since date.
		var memberSinceDate = currentUser.CreatedAt
		if currentUser.Certified {
			if dt, err := currentUser.CertifiedSince(); err == nil {
				memberSinceDate = dt
			}
		}

		var resp = Response{
			OK: true,
			ProfileFields: []ProfileField{
				{
					Name:  "Certified since",
					Value: fmt.Sprintf("%s ago", utility.FormatDurationCoarse(time.Since(memberSinceDate))),
				},
				{
					Name:  "📸 Gallery",
					Value: fmt.Sprintf("At least %d photo%s", photoCount, templates.Pluralize(photoCount)),
				},
				{
					Name:  "Age",
					Value: currentUser.GetDisplayAge(),
				},
				{
					Name:  "Gender",
					Value: gender,
				},
				{
					Name:  "City",
					Value: currentUser.GetProfileFieldOr("city", "n/a"),
				},
				{
					Name:  "Job",
					Value: currentUser.GetProfileFieldOr("job", "n/a"),
				},
				{
					Name:  "Marital status",
					Value: maritalStatus,
				},
				{
					Name:  "Orientation",
					Value: currentUser.GetProfileFieldOr("orientation", "n/a"),
				},
				{
					Name:  "Here for",
					Value: strings.Join(strings.Split(currentUser.GetProfileFieldOr("here_for", "n/a"), ","), ", "),
				},
			},
		}

		// Send success response.
		api.SendJSON(w, http.StatusOK, resp)
	})
}