// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package web

import (
	"context"
	"net/http"
	"net/http/httputil"
	"os"
	"strconv"
	"time"

	"github.com/sirupsen/logrus"

	"github.com/drone/drone/core"
	"github.com/drone/drone/logger"
	"github.com/drone/go-scm/scm"
)

// this is intended for local testing and instructs the handler
// to print the contents of the hook to stdout.
var debugPrintHook = false

func init() {
	debugPrintHook, _ = strconv.ParseBool(
		os.Getenv("DRONE_DEBUG_DUMP_HOOK"),
	)
}

// HandleHook returns an http.HandlerFunc that handles webhooks
// triggered by source code management.
func HandleHook(
	repos core.RepositoryStore,
	builds core.BuildStore,
	triggerer core.Triggerer,
	parser core.HookParser,
) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		if debugPrintHook {
			// if DRONE_DEBUG_DUMP_HOOK=true print the http.Request
			// headers and body to stdout.
			out, _ := httputil.DumpRequest(r, true)
			os.Stderr.Write(out)
		}

		hook, remote, err := parser.Parse(r, func(slug string) string {
			namespace, name := scm.Split(slug)
			repo, err := repos.FindName(r.Context(), namespace, name)
			if err != nil {
				logrus.WithFields(
					logrus.Fields{
						"namespace": namespace,
						"name":      name,
					}).Debugln("cannot find repository")
				return ""
			}
			return repo.Signer
		})

		if err != nil {
			logrus.Debugf("cannot parse webhook: %s", err)
			writeBadRequest(w, err)
			return
		}

		if hook == nil {
			logrus.Debugf("webhook ignored")
			return
		}

		// TODO handle ping requests
		// TODO consider using scm.Repository in the function callback.

		log := logrus.WithFields(logrus.Fields{
			"namespace": remote.Namespace,
			"name":      remote.Name,
			"event":     hook.Event,
			"commit":    hook.After,
		})

		log.Debugln("webhook parsed")

		repo, err := repos.FindName(r.Context(), remote.Namespace, remote.Name)
		if err != nil {
			log = log.WithError(err)
			log.Debugln("cannot find repository")
			writeNotFound(w, err)
			return
		}

		if !repo.Active {
			log.Debugln("ignore webhook, repository inactive")
			w.WriteHeader(200)
			return
		}

		ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
		ctx = logger.WithContext(ctx, log)
		defer cancel()

		if hook.Event == core.EventPush && hook.Action == core.ActionDelete {
			log.WithField("branch", hook.Target).Debugln("branch deleted")
			builds.DeleteBranch(ctx, repo.ID, hook.Target)
			w.WriteHeader(http.StatusNoContent)
			return
		}
		if hook.Event == core.EventPullRequest && hook.Action == core.ActionClose {
			log.WithField("ref", hook.Ref).Debugln("pull request closed")
			builds.DeletePull(ctx, repo.ID, scm.ExtractPullRequest(hook.Ref))
			w.WriteHeader(http.StatusNoContent)
			return
		}

		builds, err := triggerer.Trigger(ctx, repo, hook)
		if err != nil {
			writeError(w, err)
			return
		}

		writeJSON(w, builds, 200)
	}
}
