package gitlab

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"gopkg.in/go-playground/webhooks.v3"
)

// Webhook instance contains all methods needed to process events
type Webhook struct {
	provider   webhooks.Provider
	secret     string
	eventFuncs map[Event]webhooks.ProcessPayloadFunc
}

// Config defines the configuration to create a new GitHub Webhook instance
type Config struct {
	Secret string
}

// Event defines a GitHub hook event type
type Event string

// GitLab hook types
const (
	PushEvents               Event = "Push Hook"
	TagEvents                Event = "Tag Push Hook"
	IssuesEvents             Event = "Issue Hook"
	ConfidentialIssuesEvents Event = "Confidential Issue Hook"
	CommentEvents            Event = "Note Hook"
	MergeRequestEvents       Event = "Merge Request Hook"
	WikiPageEvents           Event = "Wiki Page Hook"
	PipelineEvents           Event = "Pipeline Hook"
	BuildEvents              Event = "Build Hook"
)

// New creates and returns a WebHook instance denoted by the Provider type
func New(config *Config) *Webhook {
	return &Webhook{
		provider:   webhooks.GitLab,
		secret:     config.Secret,
		eventFuncs: map[Event]webhooks.ProcessPayloadFunc{},
	}
}

// Provider returns the current hooks provider ID
func (hook Webhook) Provider() webhooks.Provider {
	return hook.provider
}

// RegisterEvents registers the function to call when the specified event(s) are encountered
func (hook Webhook) RegisterEvents(fn webhooks.ProcessPayloadFunc, events ...Event) {

	for _, event := range events {
		hook.eventFuncs[event] = fn
	}
}

// ParsePayload parses and verifies the payload and fires off the mapped function, if it exists.
func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) {
	webhooks.DefaultLog.Info("Parsing Payload...")

	event := r.Header.Get("X-Gitlab-Event")
	if len(event) == 0 {
		webhooks.DefaultLog.Error("Missing X-Gitlab-Event Header")
		http.Error(w, "400 Bad Request - Missing X-Gitlab-Event Header", http.StatusBadRequest)
		return
	}
	webhooks.DefaultLog.Debug(fmt.Sprintf("X-Gitlab-Event:%s", event))

	gitLabEvent := Event(event)

	fn, ok := hook.eventFuncs[gitLabEvent]
	// if no event registered
	if !ok {
		webhooks.DefaultLog.Info(fmt.Sprintf("Webhook Event %s not registered, it is recommended to setup only events in gitlab that will be registered in the webhook to avoid unnecessary traffic and reduce potential attack vectors.", event))
		return
	}

	payload, err := ioutil.ReadAll(r.Body)
	if err != nil || len(payload) == 0 {
		webhooks.DefaultLog.Error("Issue reading Payload")
		http.Error(w, "Error reading Payload", http.StatusInternalServerError)
		return
	}
	webhooks.DefaultLog.Debug(fmt.Sprintf("Payload:%s", string(payload)))

	// If we have a Secret set, we should check the MAC
	if len(hook.secret) > 0 {
		webhooks.DefaultLog.Info("Checking secret")
		signature := r.Header.Get("X-Gitlab-Token")
		if signature != hook.secret {
			webhooks.DefaultLog.Error(fmt.Sprintf("Invalid X-Gitlab-Token of '%s'", signature))
			http.Error(w, "403 Forbidden - Token missmatch", http.StatusForbidden)
			return
		}
	}

	// Make headers available to ProcessPayloadFunc as a webhooks type
	hd := webhooks.Header(r.Header)

	switch gitLabEvent {
	case PushEvents:
		var pe PushEventPayload
		json.Unmarshal([]byte(payload), &pe)
		hook.runProcessPayloadFunc(fn, pe, hd)

	case TagEvents:
		var te TagEventPayload
		json.Unmarshal([]byte(payload), &te)
		hook.runProcessPayloadFunc(fn, te, hd)

	case ConfidentialIssuesEvents:
		var cie ConfidentialIssueEventPayload
		json.Unmarshal([]byte(payload), &cie)
		hook.runProcessPayloadFunc(fn, cie, hd)

	case IssuesEvents:
		var ie IssueEventPayload
		json.Unmarshal([]byte(payload), &ie)
		hook.runProcessPayloadFunc(fn, ie, hd)

	case CommentEvents:
		var ce CommentEventPayload
		json.Unmarshal([]byte(payload), &ce)
		hook.runProcessPayloadFunc(fn, ce, hd)

	case MergeRequestEvents:
		var mre MergeRequestEventPayload
		json.Unmarshal([]byte(payload), &mre)
		hook.runProcessPayloadFunc(fn, mre, hd)

	case WikiPageEvents:
		var wpe WikiPageEventPayload
		json.Unmarshal([]byte(payload), &wpe)
		hook.runProcessPayloadFunc(fn, wpe, hd)

	case PipelineEvents:
		var pe PipelineEventPayload
		json.Unmarshal([]byte(payload), &pe)
		hook.runProcessPayloadFunc(fn, pe, hd)

	case BuildEvents:
		var be BuildEventPayload
		json.Unmarshal([]byte(payload), &be)
		hook.runProcessPayloadFunc(fn, be, hd)
	}
}

func (hook Webhook) runProcessPayloadFunc(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) {
	go func(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) {
		fn(results, header)
	}(fn, results, header)
}
