package repo

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

	"code.gitea.io/gitea/models/webhook"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/convert"
	"code.gitea.io/gitea/modules/util"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/routers/api/v1/utils"
	webhook_service "code.gitea.io/gitea/services/webhook"
	hat_webhook "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/webhook"
	hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
	hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)

func CreateHook(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*hat_api.CreateHookOption)
	if !checkCreateHookOption(ctx, form) {
		return
	}
	addRepoHook(ctx, form)
}

func addRepoHook(ctx *context.APIContext, form *hat_api.CreateHookOption) {
	repo := ctx.Repo
	hook, ok := addHook(ctx, form, 0, repo.Repository.ID)
	if ok {
		ctx.JSON(http.StatusCreated, convert.ToHook(repo.RepoLink, hook))
	}
}

func checkCreateHookOption(ctx *context.APIContext, form *hat_api.CreateHookOption) bool {
	if !isValidHookTaskType(form.Type) {
		ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Invalid hook type: %s", form.Type))
		return false
	}
	for _, name := range []string{"url", "content_type"} {
		if _, ok := form.Config[name]; !ok {
			ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: "+name)
			return false
		}
	}
	if !webhook.IsValidHookContentType(form.Config["content_type"]) {
		ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
		return false
	}
	if !isValidHookHttpMethod(form.Config["http_method"]) {
		ctx.Error(http.StatusUnprocessableEntity, "", "Invalid http method")
		return false
	}

	return true
}

func isValidHookHttpMethod(name string) bool {
	if name == "POST" || name == "GET" {
		return true
	}
	return false
}

func isValidHookTaskType(name string) bool {
	// 建木devops
	if name == "jianmu" {
		return true
	}

	return webhook_service.IsValidHookTaskType(name)
}

func addHook(ctx *context.APIContext, form *hat_api.CreateHookOption, orgID, repoID int64) (*webhook.Webhook, bool) {
	if len(form.Events) == 0 {
		form.Events = []string{"push"}
	}

	w := &webhook.Webhook{
		OrgID:       orgID,
		RepoID:      repoID,
		URL:         form.Config["url"],
		ContentType: webhook.ToHookContentType(form.Config["content_type"]),
		Secret:      form.Config["secret"],
		HTTPMethod:  form.Config["http_method"],
		HookEvent: &webhook.HookEvent{
			ChooseEvents: true,
			HookEvents: webhook.HookEvents{
				Create:               util.IsStringInSlice(string(webhook.HookEventCreate), form.Events, true),
				Delete:               util.IsStringInSlice(string(webhook.HookEventDelete), form.Events, true),
				Fork:                 util.IsStringInSlice(string(webhook.HookEventFork), form.Events, true),
				Issues:               issuesHook(form.Events, "issues_only"),
				IssueAssign:          issuesHook(form.Events, string(webhook.HookEventIssueAssign)),
				IssueLabel:           issuesHook(form.Events, string(webhook.HookEventIssueLabel)),
				IssueMilestone:       issuesHook(form.Events, string(webhook.HookEventIssueMilestone)),
				IssueComment:         issuesHook(form.Events, string(webhook.HookEventIssueComment)),
				Push:                 util.IsStringInSlice(string(webhook.HookEventPush), form.Events, true),
				PullRequest:          pullHook(form.Events, "pull_request_only"),
				PullRequestAssign:    pullHook(form.Events, string(webhook.HookEventPullRequestAssign)),
				PullRequestLabel:     pullHook(form.Events, string(webhook.HookEventPullRequestLabel)),
				PullRequestMilestone: pullHook(form.Events, string(webhook.HookEventPullRequestMilestone)),
				PullRequestComment:   pullHook(form.Events, string(webhook.HookEventPullRequestComment)),
				PullRequestReview:    pullHook(form.Events, "pull_request_review"),
				PullRequestSync:      pullHook(form.Events, string(webhook.HookEventPullRequestSync)),
				Wiki:                 util.IsStringInSlice(string(webhook.HookEventWiki), form.Events, true),
				Repository:           util.IsStringInSlice(string(webhook.HookEventRepository), form.Events, true),
				Release:              util.IsStringInSlice(string(webhook.HookEventRelease), form.Events, true),
			},
			BranchFilter: form.BranchFilter,
		},
		IsActive: form.Active,
		Type:     form.Type,
	}

	if w.Type == webhook.SLACK {
		channel, ok := form.Config["channel"]
		if !ok {
			ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel")
			return nil, false
		}
		channel = strings.TrimSpace(channel)

		if !webhook_service.IsValidSlackChannel(channel) {
			ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name")
			return nil, false
		}

		meta, err := json.Marshal(&webhook_service.SlackMeta{
			Channel:  channel,
			Username: form.Config["username"],
			IconURL:  form.Config["icon_url"],
			Color:    form.Config["color"],
		})
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "slack: JSON marshal failed", err)
			return nil, false
		}
		w.Meta = string(meta)
	}

	if err := w.UpdateEvent(); err != nil {
		ctx.Error(http.StatusInternalServerError, "UpdateEvent", err)
		return nil, false
	} else if err := webhook.CreateWebhook(ctx, w); err != nil {
		ctx.Error(http.StatusInternalServerError, "CreateWebhook", err)
		return nil, false
	}
	return w, true
}

func issuesHook(events []string, event string) bool {
	return util.IsStringInSlice(event, events, true) || util.IsStringInSlice(string(webhook.HookEventIssues), events, true)
}

func pullHook(events []string, event string) bool {
	return util.IsStringInSlice(event, events, true) || util.IsStringInSlice(string(webhook.HookEventPullRequest), events, true)
}

func ListHookTask(ctx *context.APIContext) {
	listOpts := utils.GetListOptions(ctx)

	hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
	if err != nil {
		ctx.NotFound()
		return
	}

	hookTasks, err := hat_webhook.GetHookTasksByHookID(hook.ID, listOpts.Page, listOpts.PageSize)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetHookTasksByHookID", err)
		return
	}

	apiHookTasks := make([]*hat_api.HookTask, len(hookTasks))
	for i := range hookTasks {
		apiHookTasks[i] = hat_convert.ToHookTask(hookTasks[i])
	}

	ctx.JSON(http.StatusOK, &apiHookTasks)
}
