// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package structs

import (
	"errors"
	"strings"
	"time"

	"gitea.dev/modules/json"
)

// ErrInvalidReceiveHook FIXME
var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webhook")

// Hook a hook is a web hook when one repository changed
type Hook struct {
	// The unique identifier of the webhook
	ID int64 `json:"id"`
	// Optional human-readable name for the webhook
	Name string `json:"name"`
	// The type of the webhook (e.g., gitea, slack, discord)
	Type string `json:"type"`
	// Branch filter pattern to determine which branches trigger the webhook
	BranchFilter string `json:"branch_filter"`
	// The URL of the webhook endpoint (hidden in JSON)
	URL string `json:"-"`
	// Configuration settings for the webhook
	Config map[string]string `json:"config"`
	// List of events that trigger this webhook
	Events []string `json:"events"`
	// Authorization header to include in webhook requests
	AuthorizationHeader string `json:"authorization_header"`
	// Whether the webhook is active and will be triggered
	Active bool `json:"active"`
	// swagger:strfmt date-time
	// The date and time when the webhook was last updated
	Updated time.Time `json:"updated_at"`
	// swagger:strfmt date-time
	// The date and time when the webhook was created
	Created time.Time `json:"created_at"`
}

// HookList represents a list of API hook.
type HookList []*Hook

// CreateHookOptionConfig has all config options in it
// required are "content_type" and "url" Required
type CreateHookOptionConfig map[string]string

// CreateHookOption options when create a hook
type CreateHookOption struct {
	// required: true
	// enum: ["dingtalk","discord","gitea","gogs","msteams","slack","telegram","feishu","wechatwork","packagist"]
	// The type of the webhook to create
	Type string `json:"type" binding:"Required"`
	// required: true
	// Configuration settings for the webhook
	Config CreateHookOptionConfig `json:"config" binding:"Required"`
	// List of events that will trigger this webhook
	Events []string `json:"events"`
	// Branch filter pattern to determine which branches trigger the webhook
	BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
	// Authorization header to include in webhook requests
	AuthorizationHeader string `json:"authorization_header"`
	// default: false
	// Whether the webhook should be active upon creation
	Active bool `json:"active"`
	// Optional human-readable name for the webhook
	Name string `json:"name" binding:"MaxSize(255)"`
}

// EditHookOption options when modify one hook
type EditHookOption struct {
	// Configuration settings for the webhook
	Config map[string]string `json:"config"`
	// List of events that trigger this webhook
	Events []string `json:"events"`
	// Branch filter pattern to determine which branches trigger the webhook
	BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
	// Authorization header to include in webhook requests
	AuthorizationHeader string `json:"authorization_header"`
	// Whether the webhook is active and will be triggered
	Active *bool `json:"active"`
	// Optional human-readable name
	Name *string `json:"name,omitzero" binding:"MaxSize(255)"`
}

// Payloader payload is some part of one hook
type Payloader interface {
	JSONPayload() ([]byte, error)
}

// PayloadUser represents the author or committer of a commit
type PayloadUser struct {
	// Full name of the commit author
	Name string `json:"name"`
	// swagger:strfmt email
	Email string `json:"email"`
	// username of the user
	UserName string `json:"username"`
}

// FIXME: consider using same format as API when commits API are added.
//        applies to PayloadCommit and PayloadCommitVerification

// PayloadCommit represents a commit
type PayloadCommit struct {
	// sha1 hash of the commit
	ID string `json:"id"`
	// The commit message
	Message string `json:"message"`
	// The URL to view this commit
	URL string `json:"url"`
	// The author of the commit
	Author *PayloadUser `json:"author"`
	// The committer of the commit
	Committer *PayloadUser `json:"committer"`
	// GPG verification information for the commit
	Verification *PayloadCommitVerification `json:"verification"`
	// swagger:strfmt date-time
	// The timestamp when the commit was made
	Timestamp time.Time `json:"timestamp"`
	// List of files added in this commit
	Added []string `json:"added"`
	// List of files removed in this commit
	Removed []string `json:"removed"`
	// List of files modified in this commit
	Modified []string `json:"modified"`
}

// PayloadCommitVerification represents the GPG verification of a commit
type PayloadCommitVerification struct {
	// Whether the commit signature is verified
	Verified bool `json:"verified"`
	// The reason for the verification status
	Reason string `json:"reason"`
	// The GPG signature of the commit
	Signature string `json:"signature"`
	// The user who signed the commit
	Signer *PayloadUser `json:"signer"`
	// The signed payload content
	Payload string `json:"payload"`
}

var (
	_ Payloader = &CreatePayload{}
	_ Payloader = &DeletePayload{}
	_ Payloader = &ForkPayload{}
	_ Payloader = &PushPayload{}
	_ Payloader = &IssuePayload{}
	_ Payloader = &IssueCommentPayload{}
	_ Payloader = &PullRequestPayload{}
	_ Payloader = &RepositoryPayload{}
	_ Payloader = &ReleasePayload{}
	_ Payloader = &PackagePayload{}
)

// CreatePayload represents a payload information of create event.
type CreatePayload struct {
	// The SHA hash of the created reference
	Sha string `json:"sha"`
	// The full name of the created reference
	Ref string `json:"ref"`
	// The type of reference created (branch or tag)
	RefType string `json:"ref_type"`
	// The repository where the reference was created
	Repo *Repository `json:"repository"`
	// The user who created the reference
	Sender *User `json:"sender"`
}

// JSONPayload return payload information
func (p *CreatePayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// ParseCreateHook parses create event hook content.
func ParseCreateHook(raw []byte) (*CreatePayload, error) {
	hook := new(CreatePayload)
	if err := json.Unmarshal(raw, hook); err != nil {
		return nil, err
	}

	// it is possible the JSON was parsed, however,
	// was not from Gogs (maybe was from Bitbucket)
	// So we'll check to be sure certain key fields
	// were populated
	switch {
	case hook.Repo == nil:
		return nil, ErrInvalidReceiveHook
	case len(hook.Ref) == 0:
		return nil, ErrInvalidReceiveHook
	}
	return hook, nil
}

// PusherType define the type to push
type PusherType string

// describe all the PusherTypes
const (
	PusherTypeUser PusherType = "user"
)

// DeletePayload represents delete payload
type DeletePayload struct {
	// The name of the deleted reference
	Ref string `json:"ref"`
	// The type of reference deleted (branch or tag)
	RefType string `json:"ref_type"`
	// The type of entity that performed the deletion
	PusherType PusherType `json:"pusher_type"`
	// The repository where the reference was deleted
	Repo *Repository `json:"repository"`
	// The user who deleted the reference
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *DeletePayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// ForkPayload represents fork payload
type ForkPayload struct {
	// The forked repository (the new fork)
	Forkee *Repository `json:"forkee"`
	// The original repository that was forked
	Repo *Repository `json:"repository"`
	// The user who created the fork
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *ForkPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// HookIssueCommentAction defines hook issue comment action
type HookIssueCommentAction string

// all issue comment actions
const (
	HookIssueCommentCreated HookIssueCommentAction = "created"
	HookIssueCommentEdited  HookIssueCommentAction = "edited"
	HookIssueCommentDeleted HookIssueCommentAction = "deleted"
)

// IssueCommentPayload represents a payload information of issue comment event.
type IssueCommentPayload struct {
	// The action performed on the comment (created, edited, deleted)
	Action HookIssueCommentAction `json:"action"`
	// The issue that the comment belongs to
	Issue *Issue `json:"issue"`
	// The pull request if the comment is on a pull request
	PullRequest *PullRequest `json:"pull_request,omitempty"`
	// The comment that was acted upon
	Comment *Comment `json:"comment"`
	// Changes made to the comment (for edit actions)
	Changes *ChangesPayload `json:"changes,omitempty"`
	// The repository containing the issue/pull request
	Repository *Repository `json:"repository"`
	// The user who performed the action
	Sender *User `json:"sender"`
	// Whether this comment is on a pull request
	IsPull bool `json:"is_pull"`
}

// JSONPayload implements Payload
func (p *IssueCommentPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// HookReleaseAction defines hook release action type
type HookReleaseAction string

// all release actions
const (
	HookReleasePublished HookReleaseAction = "published"
	HookReleaseUpdated   HookReleaseAction = "updated"
	HookReleaseDeleted   HookReleaseAction = "deleted"
)

// ReleasePayload represents a payload information of release event.
type ReleasePayload struct {
	// The action performed on the release (published, updated, deleted)
	Action HookReleaseAction `json:"action"`
	// The release that was acted upon
	Release *Release `json:"release"`
	// The repository containing the release
	Repository *Repository `json:"repository"`
	// The user who performed the action
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *ReleasePayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// PushPayload represents a payload information of push event.
type PushPayload struct {
	// The full name of the pushed reference
	Ref string `json:"ref"`
	// The SHA of the most recent commit before the push
	Before string `json:"before"`
	// The SHA of the most recent commit after the push
	After string `json:"after"`
	// URL to compare the changes in this push
	CompareURL string `json:"compare_url"`
	// List of commits included in the push
	Commits []*PayloadCommit `json:"commits"`
	// Total number of commits in the push
	TotalCommits int `json:"total_commits"`
	// The most recent commit in the push
	HeadCommit *PayloadCommit `json:"head_commit"`
	// The repository that was pushed to
	Repo *Repository `json:"repository"`
	// The user who performed the push
	Pusher *User `json:"pusher"`
	// The user who triggered the webhook
	Sender *User `json:"sender"`
}

// JSONPayload FIXME
func (p *PushPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// ParsePushHook parses push event hook content.
func ParsePushHook(raw []byte) (*PushPayload, error) {
	hook := new(PushPayload)
	if err := json.Unmarshal(raw, hook); err != nil {
		return nil, err
	}

	switch {
	case hook.Repo == nil:
		return nil, ErrInvalidReceiveHook
	case len(hook.Ref) == 0:
		return nil, ErrInvalidReceiveHook
	}
	return hook, nil
}

// Branch returns branch name from a payload
func (p *PushPayload) Branch() string {
	return strings.ReplaceAll(p.Ref, "refs/heads/", "")
}

// HookIssueAction FIXME
type HookIssueAction string

const (
	// HookIssueOpened opened
	HookIssueOpened HookIssueAction = "opened"
	// HookIssueClosed closed
	HookIssueClosed HookIssueAction = "closed"
	// HookIssueReOpened reopened
	HookIssueReOpened HookIssueAction = "reopened"
	// HookIssueEdited edited
	HookIssueEdited HookIssueAction = "edited"
	// HookIssueDeleted is an issue action for deleting an issue
	HookIssueDeleted HookIssueAction = "deleted"
	// HookIssueAssigned assigned
	HookIssueAssigned HookIssueAction = "assigned"
	// HookIssueUnassigned unassigned
	HookIssueUnassigned HookIssueAction = "unassigned"
	// HookIssueLabelUpdated label_updated
	HookIssueLabelUpdated HookIssueAction = "label_updated"
	// HookIssueLabelCleared label_cleared
	HookIssueLabelCleared HookIssueAction = "label_cleared"
	// HookIssueSynchronized synchronized
	HookIssueSynchronized HookIssueAction = "synchronized"
	// HookIssueMilestoned is an issue action for when a milestone is set on an issue.
	HookIssueMilestoned HookIssueAction = "milestoned"
	// HookIssueDemilestoned is an issue action for when a milestone is cleared on an issue.
	HookIssueDemilestoned HookIssueAction = "demilestoned"
	// HookIssueReviewed is an issue action for when a pull request is reviewed
	HookIssueReviewed HookIssueAction = "reviewed"
	// HookIssueReviewRequested is an issue action for when a reviewer is requested for a pull request.
	HookIssueReviewRequested HookIssueAction = "review_requested"
	// HookIssueReviewRequestRemoved is an issue action for removing a review request to someone on a pull request.
	HookIssueReviewRequestRemoved HookIssueAction = "review_request_removed"
)

// IssuePayload represents the payload information that is sent along with an issue event.
type IssuePayload struct {
	// The action performed on the issue
	Action HookIssueAction `json:"action"`
	// The index number of the issue
	Index int64 `json:"number"`
	// Changes made to the issue (for edit actions)
	Changes *ChangesPayload `json:"changes,omitempty"`
	// The issue that was acted upon
	Issue *Issue `json:"issue"`
	// The repository containing the issue
	Repository *Repository `json:"repository"`
	// The user who performed the action
	Sender *User `json:"sender"`
	// The commit ID related to the issue action
	CommitID string `json:"commit_id"`
}

// JSONPayload encodes the IssuePayload to JSON, with an indentation of two spaces.
func (p *IssuePayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// ChangesFromPayload FIXME
type ChangesFromPayload struct {
	// The previous value before the change
	From string `json:"from"`
}

// ChangesPayload represents the payload information of issue change
type ChangesPayload struct {
	// Changes made to the title
	Title *ChangesFromPayload `json:"title,omitempty"`
	// Changes made to the body/description
	Body *ChangesFromPayload `json:"body,omitempty"`
	// Changes made to the reference
	Ref *ChangesFromPayload `json:"ref,omitempty"`
	// Changes made to the labels added
	AddedLabels []*Label `json:"added_labels"`
	// Changes made to the labels removed
	RemovedLabels []*Label `json:"removed_labels"`
}

// PullRequestPayload represents a payload information of pull request event.
type PullRequestPayload struct {
	// The action performed on the pull request
	Action HookIssueAction `json:"action"`
	// The SHA of the most recent commit on the PR head branch before the push
	Before string `json:"before,omitempty"`
	// The SHA of the most recent commit on the PR head branch after the push
	After string `json:"after,omitempty"`
	// The index number of the pull request
	Index int64 `json:"number"`
	// Changes made to the pull request (for edit actions)
	Changes *ChangesPayload `json:"changes,omitempty"`
	// The pull request that was acted upon
	PullRequest *PullRequest `json:"pull_request"`
	// The reviewer that was requested (for review request actions)
	RequestedReviewer *User `json:"requested_reviewer"`
	// The repository containing the pull request
	Repository *Repository `json:"repository"`
	// The user who performed the action
	Sender *User `json:"sender"`
	// The commit ID related to the pull request action
	CommitID string `json:"commit_id"`
	// The review information (for review actions)
	Review *ReviewPayload `json:"review"`
}

// JSONPayload FIXME
func (p *PullRequestPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// ReviewPayload FIXME
type ReviewPayload struct {
	// The type of review (approved, rejected, comment)
	Type string `json:"type"`
	// The content/body of the review
	Content string `json:"content"`
}

// HookWikiAction an action that happens to a wiki page
type HookWikiAction string

const (
	// HookWikiCreated created
	HookWikiCreated HookWikiAction = "created"
	// HookWikiEdited edited
	HookWikiEdited HookWikiAction = "edited"
	// HookWikiDeleted deleted
	HookWikiDeleted HookWikiAction = "deleted"
)

// WikiPayload payload for repository webhooks
type WikiPayload struct {
	// The action performed on the wiki page
	Action HookWikiAction `json:"action"`
	// The repository containing the wiki
	Repository *Repository `json:"repository"`
	// The user who performed the action
	Sender *User `json:"sender"`
	// The name of the wiki page
	Page string `json:"page"`
	// The comment/commit message for the wiki change
	Comment string `json:"comment"`
}

// JSONPayload JSON representation of the payload
func (p *WikiPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", " ")
}

// HookRepoAction an action that happens to a repo
type HookRepoAction string

const (
	// HookRepoCreated created
	HookRepoCreated HookRepoAction = "created"
	// HookRepoDeleted deleted
	HookRepoDeleted HookRepoAction = "deleted"
)

// RepositoryPayload payload for repository webhooks
type RepositoryPayload struct {
	// The action performed on the repository
	Action HookRepoAction `json:"action"`
	// The repository that was acted upon
	Repository *Repository `json:"repository"`
	// The organization that owns the repository (if applicable)
	Organization *User `json:"organization"`
	// The user who performed the action
	Sender *User `json:"sender"`
}

// JSONPayload JSON representation of the payload
func (p *RepositoryPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", " ")
}

// HookPackageAction an action that happens to a package
type HookPackageAction string

const (
	// HookPackageCreated created
	HookPackageCreated HookPackageAction = "created"
	// HookPackageDeleted deleted
	HookPackageDeleted HookPackageAction = "deleted"
)

// PackagePayload represents a package payload
type PackagePayload struct {
	// The action performed on the package
	Action HookPackageAction `json:"action"`
	// The repository associated with the package
	Repository *Repository `json:"repository"`
	// The package that was acted upon
	Package *Package `json:"package"`
	// The organization that owns the package (if applicable)
	Organization *Organization `json:"organization"`
	// The user who performed the action
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *PackagePayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// WorkflowDispatchPayload represents a workflow dispatch payload
type WorkflowDispatchPayload struct {
	// The name or path of the workflow file
	Workflow string `json:"workflow"`
	// The git reference (branch, tag, or commit SHA) to run the workflow on
	Ref string `json:"ref"`
	// Input parameters for the workflow dispatch event
	Inputs map[string]any `json:"inputs"`
	// The repository containing the workflow
	Repository *Repository `json:"repository"`
	// The user who triggered the workflow dispatch
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *WorkflowDispatchPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// WorkflowCallPayload is persisted on a reusable workflow caller job's CallPayload field.
type WorkflowCallPayload struct {
	Workflow   string         `json:"workflow"`
	Ref        string         `json:"ref"`
	Inputs     map[string]any `json:"inputs"`
	Repository *Repository    `json:"repository"`
	Sender     *User          `json:"sender"`
}

// JSONPayload implements Payload
func (p *WorkflowCallPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// CommitStatusPayload represents a payload information of commit status event.
type CommitStatusPayload struct {
	// TODO: add Branches per https://docs.github.com/en/webhooks/webhook-events-and-payloads#status
	// The commit that the status is associated with
	Commit *PayloadCommit `json:"commit"`
	// The context/identifier for this status check
	Context string `json:"context"`
	// swagger:strfmt date-time
	// The date and time when the status was created
	CreatedAt time.Time `json:"created_at"`
	// A short description of the status
	Description string `json:"description"`
	// The unique identifier of the status
	ID int64 `json:"id"`
	// The repository containing the commit
	Repo *Repository `json:"repository"`
	// The user who created the status
	Sender *User `json:"sender"`
	// The SHA hash of the commit
	SHA string `json:"sha"`
	// The state of the status (pending, success, error, failure)
	State string `json:"state"`
	// The target URL to associate with this status
	TargetURL string `json:"target_url"`
	// swagger:strfmt date-time
	// The date and time when the status was last updated
	UpdatedAt *time.Time `json:"updated_at"`
}

// JSONPayload implements Payload
func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// WorkflowRunPayload represents a payload information of workflow run event.
type WorkflowRunPayload struct {
	// The action performed on the workflow run
	Action string `json:"action"`
	// The workflow definition
	Workflow *ActionWorkflow `json:"workflow"`
	// The workflow run that was acted upon
	WorkflowRun *ActionWorkflowRun `json:"workflow_run"`
	// The pull request associated with the workflow run (if applicable)
	PullRequest *PullRequest `json:"pull_request,omitempty"`
	// The organization that owns the repository (if applicable)
	Organization *Organization `json:"organization,omitempty"`
	// The repository containing the workflow
	Repo *Repository `json:"repository"`
	// The user who triggered the workflow run
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *WorkflowRunPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}

// WorkflowJobPayload represents a payload information of workflow job event.
type WorkflowJobPayload struct {
	// The action performed on the workflow job
	Action string `json:"action"`
	// The workflow job that was acted upon
	WorkflowJob *ActionWorkflowJob `json:"workflow_job"`
	// The pull request associated with the workflow job (if applicable)
	PullRequest *PullRequest `json:"pull_request,omitempty"`
	// The organization that owns the repository (if applicable)
	Organization *Organization `json:"organization,omitempty"`
	// The repository containing the workflow
	Repo *Repository `json:"repository"`
	// The user who triggered the workflow job
	Sender *User `json:"sender"`
}

// JSONPayload implements Payload
func (p *WorkflowJobPayload) JSONPayload() ([]byte, error) {
	return json.MarshalIndent(p, "", "  ")
}
