package gerrit

import (
	"context"
	"fmt"
	"net/url"
)

// DiffInfo entity contains information about the diff of a file in a revision.
type DiffInfo struct {
	MetaA           DiffFileMetaInfo  `json:"meta_a,omitempty"`
	MetaB           DiffFileMetaInfo  `json:"meta_b,omitempty"`
	ChangeType      string            `json:"change_type"`
	IntralineStatus string            `json:"intraline_status,omitempty"`
	DiffHeader      []string          `json:"diff_header"`
	Content         []DiffContent     `json:"content"`
	WebLinks        []DiffWebLinkInfo `json:"web_links,omitempty"`
	Binary          bool              `json:"binary,omitempty"`
}

// RelatedChangesInfo entity contains information about related changes.
type RelatedChangesInfo struct {
	Changes []RelatedChangeAndCommitInfo `json:"changes"`
}

// FileInfo entity contains information about a file in a patch set.
type FileInfo struct {
	Status        string `json:"status,omitempty"`
	Binary        bool   `json:"binary,omitempty"`
	OldPath       string `json:"old_path,omitempty"`
	LinesInserted int    `json:"lines_inserted,omitempty"`
	LinesDeleted  int    `json:"lines_deleted,omitempty"`
	SizeDelta     int    `json:"size_delta"`
	Size          int    `json:"size"`
}

// ActionInfo entity describes a REST API call the client can make to manipulate a resource.
// These are frequently implemented by plugins and may be discovered at runtime.
type ActionInfo struct {
	Method  string `json:"method,omitempty"`
	Label   string `json:"label,omitempty"`
	Title   string `json:"title,omitempty"`
	Enabled bool   `json:"enabled,omitempty"`
}

// CommitInfo entity contains information about a commit.
type CommitInfo struct {
	Commit    string        `json:"commit,omitempty"`
	Parents   []CommitInfo  `json:"parents"`
	Author    GitPersonInfo `json:"author"`
	Committer GitPersonInfo `json:"committer"`
	Subject   string        `json:"subject"`
	Message   string        `json:"message"`
	WebLinks  []WebLinkInfo `json:"web_links,omitempty"`
}

// MergeableInfo entity contains information about the mergeability of a change.
type MergeableInfo struct {
	SubmitType    string   `json:"submit_type"`
	Mergeable     bool     `json:"mergeable"`
	MergeableInto []string `json:"mergeable_into,omitempty"`
}

// DiffOptions specifies the parameters for GetDiff call.
type DiffOptions struct {
	// If the intraline parameter is specified, intraline differences are included in the diff.
	Intraline bool `url:"intraline,omitempty"`

	// The base parameter can be specified to control the base patch set from which the diff
	// should be generated.
	Base string `url:"base,omitempty"`

	// The integer-valued request parameter parent can be specified to control the parent commit number
	// against which the diff should be generated. This is useful for supporting review of merge commits.
	// The value is the 1-based index of the parent’s position in the commit object.
	Parent int `url:"parent,omitempty"`

	// If the weblinks-only parameter is specified, only the diff web links are returned.
	WeblinksOnly bool `url:"weblinks-only,omitempty"`

	// The ignore-whitespace parameter can be specified to control how whitespace differences are reported in the result. Valid values are NONE, TRAILING, CHANGED or ALL.
	IgnoreWhitespace string `url:"ignore-whitespace,omitempty"`

	// The context parameter can be specified to control the number of lines of surrounding context in the diff.
	// Valid values are ALL or number of lines.
	Context string `url:"context,omitempty"`
}

// CommitOptions specifies the parameters for GetCommit call.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-commit
type CommitOptions struct {
	// Adding query parameter links (for example /changes/.../commit?links) returns a CommitInfo with the additional field web_links.
	Weblinks bool `url:"links,omitempty"`
}

// MergableOptions specifies the parameters for GetMergable call.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-mergeable
type MergableOptions struct {
	// If the other-branches parameter is specified, the mergeability will also be checked for all other branches.
	OtherBranches bool `url:"other-branches,omitempty"`
}

// FilesOptions specifies the parameters for ListFiles and ListFilesReviewed calls.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files
type FilesOptions struct {
	// The request parameter q changes the response to return a list of all files (modified or unmodified)
	// that contain that substring in the path name. This is useful to implement suggestion services
	// finding a file by partial name.
	Q string `url:"q,omitempty"`

	// The base parameter can be specified to control the base patch set from which the list of files
	// should be generated.
	//
	// Note: This option is undocumented.
	Base string `url:"base,omitempty"`

	// The integer-valued request parameter parent changes the response to return a list of the files
	// which are different in this commit compared to the given parent commit. This is useful for
	// supporting review of merge commits. The value is the 1-based index of the parent’s position
	// in the commit object.
	Parent int `url:"parent,omitempty"`
}

// PatchOptions specifies the parameters for GetPatch call.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-patch
type PatchOptions struct {
	// Adding query parameter zip (for example /changes/.../patch?zip) returns the patch as a single file inside of a ZIP archive.
	// Clients can expand the ZIP to obtain the plain text patch, avoiding the need for a base64 decoding step.
	// This option implies download.
	Zip bool `url:"zip,omitempty"`

	// Query parameter download (e.g. /changes/.../patch?download) will suggest the browser save the patch as commitsha1.diff.base64, for later processing by command line tools.
	Download bool `url:"download,omitempty"`

	// If the path parameter is set, the returned content is a diff of the single file that the path refers to.
	Path string `url:"path,omitempty"`
}

// GetDiff gets the diff of a file from a certain revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-diff
func (s *ChangesService) GetDiff(ctx context.Context, changeID, revisionID, fileID string, opt *DiffOptions) (*DiffInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/diff", changeID, revisionID, url.PathEscape(fileID))

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(DiffInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// GetRelatedChanges retrieves related changes of a revision.
// Related changes are changes that either depend on, or are dependencies of the revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-related-changes
func (s *ChangesService) GetRelatedChanges(ctx context.Context, changeID, revisionID string) (*RelatedChangesInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/related", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(RelatedChangesInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// GetDraft retrieves a draft comment of a revision that belongs to the calling user.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-draft
func (s *ChangesService) GetDraft(ctx context.Context, changeID, revisionID, draftID string) (*CommentInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/drafts/%s", changeID, revisionID, draftID)
	return s.getCommentInfoResponse(ctx, u)
}

// GetComment retrieves a published comment of a revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-comment
func (s *ChangesService) GetComment(ctx context.Context, changeID, revisionID, commentID string) (*CommentInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/comments/%s", changeID, revisionID, commentID)
	return s.getCommentInfoResponse(ctx, u)
}

// GetSubmitType gets the method the server will use to submit (merge) the change.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-submit-type
func (s *ChangesService) GetSubmitType(ctx context.Context, changeID, revisionID string) (string, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/submit_type", changeID, revisionID)
	return getStringResponseWithoutOptions(ctx, s.client, u)
}

// GetRevisionActions retrieves revision actions of the revision of a change.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-revision-actions
func (s *ChangesService) GetRevisionActions(ctx context.Context, changeID, revisionID string) (*map[string]ActionInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/actions", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(map[string]ActionInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// GetCommit retrieves a parsed commit of a revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-commit
func (s *ChangesService) GetCommit(ctx context.Context, changeID, revisionID string, opt *CommitOptions) (*CommitInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/commit", changeID, revisionID)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(CommitInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// GetReview retrieves a review of a revision.
//
// As response a ChangeInfo entity with detailed labels and detailed accounts is returned that describes the review of the revision.
// The revision for which the review is retrieved is contained in the revisions field.
// In addition the current_revision field is set if the revision for which the review is retrieved is the current revision of the change.
// Please note that the returned labels are always for the current patch set.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-review
func (s *ChangesService) GetReview(ctx context.Context, changeID, revisionID string) (*ChangeInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/review", changeID, revisionID)
	return s.getChangeInfoResponse(ctx, u, nil)
}

// GetMergeable gets the method the server will use to submit (merge) the change and an indicator if the change is currently mergeable.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-mergeable
func (s *ChangesService) GetMergeable(ctx context.Context, changeID, revisionID string, opt *MergableOptions) (*MergeableInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/mergeable", changeID, revisionID)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(MergeableInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// ListRevisionDrafts lists the draft comments of a revision that belong to the calling user.
// Returns a map of file paths to lists of CommentInfo entries.
// The entries in the map are sorted by file path.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-drafts
func (s *ChangesService) ListRevisionDrafts(ctx context.Context, changeID, revisionID string) (*map[string][]CommentInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/drafts/", changeID, revisionID)
	return s.getCommentInfoMapSliceResponse(ctx, u)
}

// ListRevisionComments lists the published comments of a revision.
// As result a map is returned that maps the file path to a list of CommentInfo entries.
// The entries in the map are sorted by file path and only include file (or inline) comments.
// Use the Get Change Detail endpoint to retrieve the general change message (or comment).
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-comments
func (s *ChangesService) ListRevisionComments(ctx context.Context, changeID, revisionID string) (*map[string][]CommentInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/comments/", changeID, revisionID)
	return s.getCommentInfoMapSliceResponse(ctx, u)
}

// ListFiles lists the files that were modified, added or deleted in a revision.
// As result a map is returned that maps the file path to a list of FileInfo entries.
// The entries in the map are sorted by file path.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files
func (s *ChangesService) ListFiles(ctx context.Context, changeID, revisionID string, opt *FilesOptions) (map[string]FileInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/", changeID, revisionID)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var v map[string]FileInfo
	resp, err := s.client.Do(req, &v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// ListFilesReviewed lists the files that were modified, added or deleted in a revision.
// Unlike ListFiles, the response of ListFilesReviewed is a list of the paths the caller
// has marked as reviewed. Clients that also need the FileInfo should make two requests.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files
func (s *ChangesService) ListFilesReviewed(ctx context.Context, changeID, revisionID string, opt *FilesOptions) ([]string, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/", changeID, revisionID)

	o := struct {
		// The request parameter reviewed changes the response to return a list of the paths the caller has marked as reviewed.
		Reviewed bool `url:"reviewed,omitempty"`

		FilesOptions
	}{
		Reviewed: true,
	}
	if opt != nil {
		o.FilesOptions = *opt
	}
	u, err := addOptions(u, o)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var v []string
	resp, err := s.client.Do(req, &v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// SetReview sets a review on a revision.
// The review must be provided in the request body as a ReviewInput entity.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review
func (s *ChangesService) SetReview(ctx context.Context, changeID, revisionID string, input *ReviewInput) (*ReviewResult, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/review", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "POST", u, input)
	if err != nil {
		return nil, nil, err
	}

	v := new(ReviewResult)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// PublishDraftRevision publishes a draft revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-draft-revision
func (s *ChangesService) PublishDraftRevision(ctx context.Context, changeID, revisionID string) (*Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/publish", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "POST", u, nil)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// DeleteDraftRevision deletes a draft revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-draft-revision
func (s *ChangesService) DeleteDraftRevision(ctx context.Context, changeID, revisionID string) (*Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s", changeID, revisionID)
	return s.client.DeleteRequest(ctx, u, nil)
}

// GetPatch gets the formatted patch for one revision.
//
// The formatted patch is returned as text encoded inside base64.
// Adding query parameter zip (for example /changes/.../patch?zip) returns the patch as a single file inside of a ZIP archive.
// Clients can expand the ZIP to obtain the plain text patch, avoiding the need for a base64 decoding step.
// This option implies download.
//
// Query parameter download (e.g. /changes/.../patch?download) will suggest the browser save the patch as commitsha1.diff.base64, for later processing by command line tools.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-patch
func (s *ChangesService) GetPatch(ctx context.Context, changeID, revisionID string, opt *PatchOptions) (*string, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/patch", changeID, revisionID)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(string)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// TestSubmitType tests the submit_type Prolog rule in the project, or the one given.
//
// Request body may be either the Prolog code as text/plain or a RuleInput object.
// The query parameter filters may be set to SKIP to bypass parent project filters while testing a project-specific rule.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#test-submit-type
func (s *ChangesService) TestSubmitType(ctx context.Context, changeID, revisionID string, input *RuleInput) (*string, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/test.submit_type", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "POST", u, input)
	if err != nil {
		return nil, nil, err
	}

	v := new(string)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// TestSubmitRule tests the submit_rule Prolog rule in the project, or the one given.
//
// Request body may be either the Prolog code as text/plain or a RuleInput object.
// The query parameter filters may be set to SKIP to bypass parent project filters while testing a project-specific rule.
//
// The response is a list of SubmitRecord entries describing the permutations that satisfy the tested submit rule.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#test-submit-rule
func (s *ChangesService) TestSubmitRule(ctx context.Context, changeID, revisionID string, input *RuleInput) (*[]SubmitRecord, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/test.submit_rule", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "POST", u, input)
	if err != nil {
		return nil, nil, err
	}

	v := new([]SubmitRecord)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// CreateDraft creates a draft comment on a revision.
// The new draft comment must be provided in the request body inside a CommentInput entity.
//
// As response a CommentInfo entity is returned that describes the draft comment.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#create-draft
func (s *ChangesService) CreateDraft(ctx context.Context, changeID, revisionID string, input *CommentInput) (*CommentInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/drafts", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "PUT", u, input)
	if err != nil {
		return nil, nil, err
	}

	v := new(CommentInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// UpdateDraft updates a draft comment on a revision.
// The new draft comment must be provided in the request body inside a CommentInput entity.
//
// As response a CommentInfo entity is returned that describes the draft comment.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#update-draft
func (s *ChangesService) UpdateDraft(ctx context.Context, changeID, revisionID, draftID string, input *CommentInput) (*CommentInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/drafts/%s", changeID, revisionID, draftID)

	req, err := s.client.NewRequest(ctx, "PUT", u, input)
	if err != nil {
		return nil, nil, err
	}

	v := new(CommentInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// DeleteDraft deletes a draft comment from a revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-draft
func (s *ChangesService) DeleteDraft(ctx context.Context, changeID, revisionID, draftID string) (*Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/drafts/%s", changeID, revisionID, draftID)
	return s.client.DeleteRequest(ctx, u, nil)
}

// DeleteReviewed deletes the reviewed flag of the calling user from a file of a revision.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-reviewed
func (s *ChangesService) DeleteReviewed(ctx context.Context, changeID, revisionID, fileID string) (*Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/reviewed", changeID, revisionID, url.PathEscape(fileID))
	return s.client.DeleteRequest(ctx, u, nil)
}

// GetContent gets the content of a file from a certain revision.
// The content is returned as base64 encoded string.
// The HTTP response Content-Type is always text/plain, reflecting the base64 wrapping.
// A Gerrit-specific X-FYI-Content-Type header is returned describing the server detected content type of the file.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-content
func (s *ChangesService) GetContent(ctx context.Context, changeID, revisionID, fileID string) (*string, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/content", changeID, revisionID, url.PathEscape(fileID))

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	v := new(string)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

// GetContentType gets the content type of a file from a certain revision.
// This is nearly the same as GetContent.
// But if only the content type is required, callers should use HEAD to avoid downloading the encoded file contents.
//
// For further documentation see GetContent.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-content
func (s *ChangesService) GetContentType(ctx context.Context, changeID, revisionID, fileID string) (*Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/content", changeID, revisionID, url.PathEscape(fileID))

	req, err := s.client.NewRequest(ctx, "HEAD", u, nil)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// SetReviewed marks a file of a revision as reviewed by the calling user.
//
// If the file was already marked as reviewed by the calling user the response is “200 OK”.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-reviewed
func (s *ChangesService) SetReviewed(ctx context.Context, changeID, revisionID, fileID string) (*Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/reviewed", changeID, revisionID, url.PathEscape(fileID))

	req, err := s.client.NewRequest(ctx, "PUT", u, nil)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// CherryPickRevision cherry picks a revision to a destination branch.
// The commit message and destination branch must be provided in the request body inside a CherryPickInput entity.
//
// As response a ChangeInfo entity is returned that describes the resulting cherry picked change.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#cherry-pick
func (s *ChangesService) CherryPickRevision(ctx context.Context, changeID, revisionID string, input *CherryPickInput) (*ChangeInfo, *Response, error) {
	u := fmt.Sprintf("changes/%s/revisions/%s/cherrypick", changeID, revisionID)

	req, err := s.client.NewRequest(ctx, "POST", u, input)
	if err != nil {
		return nil, nil, err
	}

	v := new(ChangeInfo)
	resp, err := s.client.Do(req, v)
	if err != nil {
		return nil, resp, err
	}

	return v, resp, err
}

/*
TODO: Missing Revision Endpoints
	Rebase Revision
	Submit Revision
	DownloadContent (https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-safe-content)
*/
