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/util"
	"code.gitea.io/gitea/modules/web"
	webhook_module "code.gitea.io/gitea/modules/webhook"
	"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"
	jsoniter "github.com/json-iterator/go"
)

func EditHook(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*hat_api.EditHookOption)
	hookID := ctx.ParamsInt64(":id")
	editRepoHook(ctx, form, hookID)
}

func editRepoHook(ctx *context.APIContext, form *hat_api.EditHookOption, hookID int64) {
	repo := ctx.Repo
	hook, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID)
	if err != nil {
		return
	}

	if !editHook(ctx, form, hook) {
		return
	}

	updated, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID)
	if err != nil {
		return
	}

	apiHook, err := webhook_service.ToHook(repo.RepoLink, updated)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "ToHook", err)
		return
	}

	ctx.JSON(http.StatusOK, apiHook)
}

func editHook(ctx *context.APIContext, form *hat_api.EditHookOption, w *webhook.Webhook) bool {
	if form.Config != nil {
		if url, ok := form.Config["url"]; ok {
			w.URL = url
		}
		if secret, ok := form.Config["secret"]; ok {
			w.Secret = secret
		}

		if ct, ok := form.Config["content_type"]; ok {
			if !webhook.IsValidHookContentType(ct) {
				ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
				return false
			}
			w.ContentType = webhook.ToHookContentType(ct)
		}

		if hw, ok := form.Config["http_method"]; ok {
			if !isValidHookHttpMethod(hw) {
				ctx.Error(http.StatusUnprocessableEntity, "", "Invalid http method")
				return false
			}
			w.HTTPMethod = hw
		}

		if w.Type == webhook_module.SLACK {
			if channel, ok := form.Config["channel"]; ok {
				json := jsoniter.ConfigCompatibleWithStandardLibrary
				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 false
				}
				w.Meta = string(meta)
			}
		}
	}

	// Update events
	if len(form.Events) == 0 {
		form.Events = []string{"push"}
	}
	w.PushOnly = false
	w.SendEverything = false
	w.ChooseEvents = true
	w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
	w.Push = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
	w.PullRequest = util.SliceContainsString(form.Events, string(webhook_module.HookEventPullRequest), true)
	// w.Create = util.IsStringInSlice(string(webhook.HookEventCreate), form.Events, true)
	w.Delete = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
	w.Fork = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
	w.Issues = util.SliceContainsString(form.Events, string(webhook_module.HookEventIssues), true)
	w.Issues = issuesHook(form.Events, "issues_only")
	w.IssueAssign = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
	w.IssueLabel = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
	w.IssueMilestone = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
	w.IssueComment = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
	// w.IssueComment = util.IsStringInSlice(string(webhook.HookEventIssueComment), form.Events, true)
	w.Push = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
	// w.PullRequest = util.IsStringInSlice(string(webhook.HookEventPullRequest), form.Events, true)
	w.PullRequest = pullHook(form.Events, "pull_request_only")
	w.PullRequestAssign = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
	w.PullRequestLabel = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
	w.PullRequestMilestone = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
	w.PullRequestComment = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
	w.PullRequestReview = pullHook(form.Events, "pull_request_review")
	w.PullRequestSync = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
	w.Repository = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
	w.Release = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
	w.BranchFilter = form.BranchFilter
	w.HookEvent.BranchFilter = form.BranchFilter

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

	if form.Active != nil {
		w.IsActive = *form.Active
	}

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

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 {
		apiHook, err := webhook_service.ToHook(repo.RepoLink, hook)
		if err != nil {
			ctx.Error(http.StatusInternalServerError, "ToHook", err)
			return
		}

		ctx.JSON(http.StatusCreated, apiHook)
	}
}

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" || name == "softbot" || name == "reposync" {
		return true
	}

	return webhook_service.IsValidHookTaskType(name)
}

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

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

	if w.Type == webhook_module.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.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventIssues), true)
}

func pullHook(events []string, event string) bool {
	return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), 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)
}
