package github_webhooks

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

	"github.com/gorilla/mux"
	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/plugins/inputs"
)

func init() {
	inputs.Add("github_webhooks", func() telegraf.Input { return &GithubWebhooks{} })
}

type GithubWebhooks struct {
	ServiceAddress string
	// Lock for the struct
	sync.Mutex
	// Events buffer to store events between Gather calls
	events []Event
}

func NewGithubWebhooks() *GithubWebhooks {
	return &GithubWebhooks{}
}

func (gh *GithubWebhooks) SampleConfig() string {
	return `
  ## Address and port to host Webhook listener on
  service_address = ":1618"
`
}

func (gh *GithubWebhooks) Description() string {
	return "A Github Webhook Event collector"
}

// Writes the points from <-gh.in to the Accumulator
func (gh *GithubWebhooks) Gather(acc telegraf.Accumulator) error {
	gh.Lock()
	defer gh.Unlock()
	for _, event := range gh.events {
		p := event.NewMetric()
		acc.AddFields("github_webhooks", p.Fields(), p.Tags(), p.Time())
	}
	gh.events = make([]Event, 0)
	return nil
}

func (gh *GithubWebhooks) Listen() {
	r := mux.NewRouter()
	r.HandleFunc("/", gh.eventHandler).Methods("POST")
	err := http.ListenAndServe(fmt.Sprintf("%s", gh.ServiceAddress), r)
	if err != nil {
		log.Printf("Error starting server: %v", err)
	}
}

func (gh *GithubWebhooks) Start(_ telegraf.Accumulator) error {
	go gh.Listen()
	log.Printf("Started the github_webhooks service on %s\n", gh.ServiceAddress)
	return nil
}

func (gh *GithubWebhooks) Stop() {
	log.Println("Stopping the ghWebhooks service")
}

// Handles the / route
func (gh *GithubWebhooks) eventHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	eventType := r.Header["X-Github-Event"][0]
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	e, err := NewEvent(data, eventType)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	gh.Lock()
	gh.events = append(gh.events, e)
	gh.Unlock()
	w.WriteHeader(http.StatusOK)
}

func newCommitComment(data []byte) (Event, error) {
	commitCommentStruct := CommitCommentEvent{}
	err := json.Unmarshal(data, &commitCommentStruct)
	if err != nil {
		return nil, err
	}
	return commitCommentStruct, nil
}

func newCreate(data []byte) (Event, error) {
	createStruct := CreateEvent{}
	err := json.Unmarshal(data, &createStruct)
	if err != nil {
		return nil, err
	}
	return createStruct, nil
}

func newDelete(data []byte) (Event, error) {
	deleteStruct := DeleteEvent{}
	err := json.Unmarshal(data, &deleteStruct)
	if err != nil {
		return nil, err
	}
	return deleteStruct, nil
}

func newDeployment(data []byte) (Event, error) {
	deploymentStruct := DeploymentEvent{}
	err := json.Unmarshal(data, &deploymentStruct)
	if err != nil {
		return nil, err
	}
	return deploymentStruct, nil
}

func newDeploymentStatus(data []byte) (Event, error) {
	deploymentStatusStruct := DeploymentStatusEvent{}
	err := json.Unmarshal(data, &deploymentStatusStruct)
	if err != nil {
		return nil, err
	}
	return deploymentStatusStruct, nil
}

func newFork(data []byte) (Event, error) {
	forkStruct := ForkEvent{}
	err := json.Unmarshal(data, &forkStruct)
	if err != nil {
		return nil, err
	}
	return forkStruct, nil
}

func newGollum(data []byte) (Event, error) {
	gollumStruct := GollumEvent{}
	err := json.Unmarshal(data, &gollumStruct)
	if err != nil {
		return nil, err
	}
	return gollumStruct, nil
}

func newIssueComment(data []byte) (Event, error) {
	issueCommentStruct := IssueCommentEvent{}
	err := json.Unmarshal(data, &issueCommentStruct)
	if err != nil {
		return nil, err
	}
	return issueCommentStruct, nil
}

func newIssues(data []byte) (Event, error) {
	issuesStruct := IssuesEvent{}
	err := json.Unmarshal(data, &issuesStruct)
	if err != nil {
		return nil, err
	}
	return issuesStruct, nil
}

func newMember(data []byte) (Event, error) {
	memberStruct := MemberEvent{}
	err := json.Unmarshal(data, &memberStruct)
	if err != nil {
		return nil, err
	}
	return memberStruct, nil
}

func newMembership(data []byte) (Event, error) {
	membershipStruct := MembershipEvent{}
	err := json.Unmarshal(data, &membershipStruct)
	if err != nil {
		return nil, err
	}
	return membershipStruct, nil
}

func newPageBuild(data []byte) (Event, error) {
	pageBuildEvent := PageBuildEvent{}
	err := json.Unmarshal(data, &pageBuildEvent)
	if err != nil {
		return nil, err
	}
	return pageBuildEvent, nil
}

func newPublic(data []byte) (Event, error) {
	publicEvent := PublicEvent{}
	err := json.Unmarshal(data, &publicEvent)
	if err != nil {
		return nil, err
	}
	return publicEvent, nil
}

func newPullRequest(data []byte) (Event, error) {
	pullRequestStruct := PullRequestEvent{}
	err := json.Unmarshal(data, &pullRequestStruct)
	if err != nil {
		return nil, err
	}
	return pullRequestStruct, nil
}

func newPullRequestReviewComment(data []byte) (Event, error) {
	pullRequestReviewCommentStruct := PullRequestReviewCommentEvent{}
	err := json.Unmarshal(data, &pullRequestReviewCommentStruct)
	if err != nil {
		return nil, err
	}
	return pullRequestReviewCommentStruct, nil
}

func newPush(data []byte) (Event, error) {
	pushStruct := PushEvent{}
	err := json.Unmarshal(data, &pushStruct)
	if err != nil {
		return nil, err
	}
	return pushStruct, nil
}

func newRelease(data []byte) (Event, error) {
	releaseStruct := ReleaseEvent{}
	err := json.Unmarshal(data, &releaseStruct)
	if err != nil {
		return nil, err
	}
	return releaseStruct, nil
}

func newRepository(data []byte) (Event, error) {
	repositoryStruct := RepositoryEvent{}
	err := json.Unmarshal(data, &repositoryStruct)
	if err != nil {
		return nil, err
	}
	return repositoryStruct, nil
}

func newStatus(data []byte) (Event, error) {
	statusStruct := StatusEvent{}
	err := json.Unmarshal(data, &statusStruct)
	if err != nil {
		return nil, err
	}
	return statusStruct, nil
}

func newTeamAdd(data []byte) (Event, error) {
	teamAddStruct := TeamAddEvent{}
	err := json.Unmarshal(data, &teamAddStruct)
	if err != nil {
		return nil, err
	}
	return teamAddStruct, nil
}

func newWatch(data []byte) (Event, error) {
	watchStruct := WatchEvent{}
	err := json.Unmarshal(data, &watchStruct)
	if err != nil {
		return nil, err
	}
	return watchStruct, nil
}

type newEventError struct {
	s string
}

func (e *newEventError) Error() string {
	return e.s
}

func NewEvent(r []byte, t string) (Event, error) {
	log.Printf("New %v event recieved", t)
	switch t {
	case "commit_comment":
		return newCommitComment(r)
	case "create":
		return newCreate(r)
	case "delete":
		return newDelete(r)
	case "deployment":
		return newDeployment(r)
	case "deployment_status":
		return newDeploymentStatus(r)
	case "fork":
		return newFork(r)
	case "gollum":
		return newGollum(r)
	case "issue_comment":
		return newIssueComment(r)
	case "issues":
		return newIssues(r)
	case "member":
		return newMember(r)
	case "membership":
		return newMembership(r)
	case "page_build":
		return newPageBuild(r)
	case "public":
		return newPublic(r)
	case "pull_request":
		return newPullRequest(r)
	case "pull_request_review_comment":
		return newPullRequestReviewComment(r)
	case "push":
		return newPush(r)
	case "release":
		return newRelease(r)
	case "repository":
		return newRepository(r)
	case "status":
		return newStatus(r)
	case "team_add":
		return newTeamAdd(r)
	case "watch":
		return newWatch(r)
	}
	return nil, &newEventError{"Not a recgonized event type"}
}