// Copyright 2018 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 coding

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

	"github.com/woodpecker-ci/woodpecker/server/model"
)

const (
	hookEvent = "X-Coding-Event"
	hookPush  = "push"
	hookPR    = "pull_request"
	hookMR    = "merge_request"
)

type User struct {
	GlobalKey string `json:"global_key"`
	Avatar    string `json:"avatar"`
}

type Repository struct {
	Name     string `json:"name"`
	HttpsURL string `json:"https_url"`
	SshURL   string `json:"ssh_url"`
	WebURL   string `json:"web_url"`
	Owner    *User  `json:"owner"`
}

type Committer struct {
	Email string `json:"email"`
	Name  string `json:"name"`
}

type Commit struct {
	SHA          string     `json:"sha"`
	ShortMessage string     `json:"short_message"`
	Committer    *Committer `json:"committer"`
}

type PullRequest MergeRequest

type MergeRequest struct {
	SourceBranch string  `json:"source_branch"`
	TargetBranch string  `json:"target_branch"`
	CommitSHA    string  `json:"merge_commit_sha"`
	Status       string  `json:"status"`
	Action       string  `json:"action"`
	Number       float64 `json:"number"`
	Title        string  `json:"title"`
	Body         string  `json:"body"`
	WebURL       string  `json:"web_url"`
	User         *User   `json:"user"`
}

type PushHook struct {
	Event      string      `json:"event"`
	Repository *Repository `json:"repository"`
	Ref        string      `json:"ref"`
	Before     string      `json:"before"`
	After      string      `json:"after"`
	Commits    []*Commit   `json:"commits"`
	User       *User       `json:"user"`
}

type PullRequestHook struct {
	Event       string       `json:"event"`
	Repository  *Repository  `json:"repository"`
	PullRequest *PullRequest `json:"pull_request"`
}

type MergeRequestHook struct {
	Event        string        `json:"event"`
	Repository   *Repository   `json:"repository"`
	MergeRequest *MergeRequest `json:"merge_request"`
}

func parseHook(r *http.Request) (*model.Repo, *model.Build, error) {
	raw, err := ioutil.ReadAll(r.Body)
	defer r.Body.Close()
	if err != nil {
		return nil, nil, err
	}

	switch r.Header.Get(hookEvent) {
	case hookPush:
		return parsePushHook(raw)
	case hookPR:
		return parsePullRequestHook(raw)
	case hookMR:
		return parseMergeReuqestHook(raw)
	}
	return nil, nil, nil
}

func findLastCommit(commits []*Commit, sha string) *Commit {
	var lastCommit *Commit
	for _, commit := range commits {
		if commit.SHA == sha {
			lastCommit = commit
			break
		}
	}
	if lastCommit == nil {
		lastCommit = &Commit{}
	}
	if lastCommit.Committer == nil {
		lastCommit.Committer = &Committer{}
	}
	return lastCommit
}

func convertRepository(repo *Repository) (*model.Repo, error) {
	// tricky stuff for a team project without a team owner instead of a user owner
	re := regexp.MustCompile(`git@.+:([^/]+)/.+\.git`)
	matches := re.FindStringSubmatch(repo.SshURL)
	if len(matches) != 2 {
		return nil, fmt.Errorf("Unable to resolve owner from ssh url %q", repo.SshURL)
	}

	return &model.Repo{
		Owner:    matches[1],
		Name:     repo.Name,
		FullName: projectFullName(repo.Owner.GlobalKey, repo.Name),
		Link:     repo.WebURL,
		Kind:     model.RepoGit,
	}, nil
}

func parsePushHook(raw []byte) (*model.Repo, *model.Build, error) {
	hook := &PushHook{}
	err := json.Unmarshal(raw, hook)
	if err != nil {
		return nil, nil, err
	}

	// no build triggered when removing ref
	if hook.After == "0000000000000000000000000000000000000000" {
		return nil, nil, nil
	}

	repo, err := convertRepository(hook.Repository)
	if err != nil {
		return nil, nil, err
	}

	lastCommit := findLastCommit(hook.Commits, hook.After)
	build := &model.Build{
		Event:   model.EventPush,
		Commit:  hook.After,
		Ref:     hook.Ref,
		Link:    fmt.Sprintf("%s/git/commit/%s", hook.Repository.WebURL, hook.After),
		Branch:  strings.Replace(hook.Ref, "refs/heads/", "", -1),
		Message: lastCommit.ShortMessage,
		Email:   lastCommit.Committer.Email,
		Avatar:  hook.User.Avatar,
		Author:  hook.User.GlobalKey,
		Remote:  hook.Repository.HttpsURL,
	}
	return repo, build, nil
}

func parsePullRequestHook(raw []byte) (*model.Repo, *model.Build, error) {
	hook := &PullRequestHook{}
	err := json.Unmarshal(raw, hook)
	if err != nil {
		return nil, nil, err
	}
	if hook.PullRequest.Status != "CANMERGE" ||
		(hook.PullRequest.Action != "create" && hook.PullRequest.Action != "synchronize") {
		return nil, nil, nil
	}

	repo, err := convertRepository(hook.Repository)
	if err != nil {
		return nil, nil, err
	}
	build := &model.Build{
		Event:   model.EventPull,
		Commit:  hook.PullRequest.CommitSHA,
		Link:    hook.PullRequest.WebURL,
		Ref:     fmt.Sprintf("refs/pull/%d/MERGE", int(hook.PullRequest.Number)),
		Branch:  hook.PullRequest.TargetBranch,
		Message: hook.PullRequest.Body,
		Author:  hook.PullRequest.User.GlobalKey,
		Avatar:  hook.PullRequest.User.Avatar,
		Title:   hook.PullRequest.Title,
		Remote:  hook.Repository.HttpsURL,
		Refspec: fmt.Sprintf("%s:%s", hook.PullRequest.SourceBranch, hook.PullRequest.TargetBranch),
	}

	return repo, build, nil
}

func parseMergeReuqestHook(raw []byte) (*model.Repo, *model.Build, error) {
	hook := &MergeRequestHook{}
	err := json.Unmarshal(raw, hook)
	if err != nil {
		return nil, nil, err
	}
	if hook.MergeRequest.Status != "CANMERGE" ||
		(hook.MergeRequest.Action != "create" && hook.MergeRequest.Action != "synchronize") {
		return nil, nil, nil
	}

	repo, err := convertRepository(hook.Repository)
	if err != nil {
		return nil, nil, err
	}

	build := &model.Build{
		Event:   model.EventPull,
		Commit:  hook.MergeRequest.CommitSHA,
		Link:    hook.MergeRequest.WebURL,
		Ref:     fmt.Sprintf("refs/merge/%d/MERGE", int(hook.MergeRequest.Number)),
		Branch:  hook.MergeRequest.TargetBranch,
		Message: hook.MergeRequest.Body,
		Author:  hook.MergeRequest.User.GlobalKey,
		Avatar:  hook.MergeRequest.User.Avatar,
		Title:   hook.MergeRequest.Title,
		Remote:  hook.Repository.HttpsURL,
		Refspec: fmt.Sprintf("%s:%s", hook.MergeRequest.SourceBranch, hook.MergeRequest.TargetBranch),
	}
	return repo, build, nil
}
