// Package kofi contains a webhook listener to be used in the Ko-fi
// API to receive information about (recurring) donations / shop orders
package kofi

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/Luzifer/go_helpers/v2/fieldcollection"
	"github.com/Luzifer/twitch-bot/v3/plugins"
	"github.com/gorilla/mux"
	"github.com/sirupsen/logrus"
)

const actorName = "kofi"

var (
	eventCreatorFunc plugins.EventHandlerFunc
	getModuleConfig  plugins.ModuleConfigGetterFunc

	ptrStringEmpty = func(s string) *string { return &s }("")
)

// Register provides the plugins.RegisterFunc
func Register(args plugins.RegistrationArguments) (err error) {
	eventCreatorFunc = args.CreateEvent
	getModuleConfig = args.GetModuleConfigForChannel

	if err = args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
		Description:  "Endpoint to handle Ko-fi Webhook posts",
		HandlerFunc:  handleKoFiPost,
		Method:       http.MethodPost,
		Module:       actorName,
		Name:         "Handle Ko-fi Webhook",
		Path:         "/webhook/{channel}",
		ResponseType: plugins.HTTPRouteResponseTypeJSON,
		RouteParams: []plugins.HTTPRouteParamDocumentation{
			{
				Description: "Channel to create the event in",
				Name:        "channel",
			},
		},
	}); err != nil {
		return fmt.Errorf("registering API route: %w", err)
	}

	return nil
}

func handleKoFiPost(w http.ResponseWriter, r *http.Request) {
	channel := mux.Vars(r)["channel"]

	channelModuleConf := getModuleConfig(actorName, channel)

	// The data is sent (posted) with a content type of application/x-www-form-urlencoded.
	// A field named 'data' contains the payment information as a JSON string.
	jsonData := r.FormValue("data")
	if jsonData == "" {
		// Well, no.
		logrus.WithField("remote_addr", r.RemoteAddr).Warn("received KoFi hook without payload")
		http.Error(w, "you missed something", http.StatusBadRequest)
		return
	}

	var (
		err     error
		payload hookPayload
	)

	// Read the payload
	if err = json.Unmarshal([]byte(jsonData), &payload); err != nil {
		logrus.WithError(err).Error("unmarshalling KoFi JSON data")
		http.Error(w, "that's not valid json, you know", http.StatusBadRequest)
		return
	}

	// If we know the verification token, validate the payload
	if validateToken := channelModuleConf.MustString("verification_token", ptrStringEmpty); validateToken != "" && payload.VerificationToken != validateToken {
		logrus.WithFields(logrus.Fields{
			"expected": fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(validateToken))),
			"provided": fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(payload.VerificationToken))),
		}).Error("received Ko-fi payload with invalid verification token")

		http.Error(w, "ehm, who are you?", http.StatusForbidden)
		return
	}

	fields := fieldcollection.NewFieldCollection()
	fields.Set("channel", "#"+strings.TrimLeft(channel, "#"))

	switch payload.Type {
	case hookTypeDonation, hookTypeSubscription:
		// Single or Recurring Donation
		fields.Set("from", payload.FromName)
		fields.Set("amount", payload.Amount)
		fields.Set("currency", payload.Currency)
		fields.Set("isSubscription", payload.IsSubscriptionPayment)
		fields.Set("isFirstSubPayment", payload.IsFirstSubscriptionPayment)

		if payload.IsPublic {
			fields.Set("message", payload.Message)
		}

		if payload.IsSubscriptionPayment && payload.TierName != nil {
			fields.Set("tier", *payload.TierName)
		}

		if err = eventCreatorFunc("kofi_donation", fields); err != nil {
			logrus.WithError(err).Error("creating kofi_donation event")
			http.Error(w, "ehm, that didn't work, I'm sorry", http.StatusInternalServerError)
			return
		}

	default:
		// Unsupported, we take that and discard it
		logrus.WithField("type", payload.Type).Warn("received unhandled hook type")
	}

	w.WriteHeader(http.StatusOK)
}