package githubcli

import (
	"context"
	"regexp"

	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"

	"github.com/shurcooL/githubv4"
)

var (
	// example:
	// * secondary confirm: another way to show and change some cluster's gito… by @jinshayumi in #103
	regexParseNotes = regexp.MustCompile(`\* (.+) by @(.+) in https://github\.com/(.+)/(.+)/pull/(.+)`)
)

type PullRequestMergeResult struct {
	SHA    *string
	Merged *bool
}

type ListPullRequestOption struct {
	Pager

	Labels []string
}

func (o ListPullRequestOption) Normalize() ListPullRequestOption {
	if o.PerPage == 0 {
		o.PerPage = 10
	}
	if o.PerPage >= 50 {
		o.PerPage = 50
	}
	return o
}

type ListPullRequestResult struct {
	Page         PagerResult
	PullRequests []PullRequest
}

type PullRequestsService interface {
	// Get a single pull request.
	Get(ctx context.Context, owner string, repo string, number int) (*PullRequest, error)
	// List pull requests.
	List(ctx context.Context, owner string, repo string, opt ListPullRequestOption) (*ListPullRequestResult, error)
	// Approve approve a pull request.
	Approve(ctx context.Context, owner, repo string, number int, body string) (*PullRequestReview, error)
	// Merge a pull request.
	//
	// If the PR is already merged, return nil and do nothing.
	Merge(ctx context.Context, owner string, repo string, number int, method githubv4.PullRequestMergeMethod) (*PullRequest, error)
	// AddComment adds a comment to a pull request.
	AddComment(ctx context.Context, owner, repo string, number int, body string) (*Comment, error)
}

var _ PullRequestsService = (*pullRequestsServiceImpl)(nil)

type pullRequestsServiceImpl GithubClient

func (p *pullRequestsServiceImpl) Get(ctx context.Context, owner string, repo string, number int) (*PullRequest, error) {
	var query struct {
		Repository struct {
			PullRequest PullRequest `graphql:"pullRequest(number: $number)"`
		} `graphql:"repository(owner: $owner, name: $repo)"`
	}

	variables := map[string]any{
		"owner":  githubv4.String(owner),
		"repo":   githubv4.String(repo),
		"number": githubv4.Int(number),
	}

	err := p.cliv4.Query(ctx, &query, variables)
	if err != nil {
		return nil, err
	}

	return &query.Repository.PullRequest, nil
}

func (p *pullRequestsServiceImpl) List(ctx context.Context, owner string, repo string, opt ListPullRequestOption) (*ListPullRequestResult, error) {
	opt = opt.Normalize()

	var query struct {
		Repository struct {
			PullRequests struct {
				Nodes    []PullRequest
				PageInfo PagerResult
			} `graphql:"pullRequests(first: $perPage, after: $cursor, labels: $labels, orderBy: { field: UPDATED_AT, direction: DESC })"`
		} `graphql:"repository(owner: $owner, name: $repoName)"`
	}

	variables := map[string]any{
		"owner":    githubv4.String(owner),
		"repoName": githubv4.String(repo),
		"labels":   (*[]githubv4.String)(nil),
		"perPage":  githubv4.Int(opt.PerPage),
		"cursor":   (*githubv4.String)(nil),
	}
	if opt.Cursor != "" {
		variables["cursor"] = githubv4.NewString(githubv4.String(opt.Cursor))
	}
	if len(opt.Labels) != 0 {
		variables["labels"] = pointer.Pointer(container.TransformSlice(opt.Labels, container.FromString[githubv4.String]))
	}

	err := p.cliv4.Query(ctx, &query, variables)
	if err != nil {
		return nil, err
	}

	result := &ListPullRequestResult{
		Page:         query.Repository.PullRequests.PageInfo,
		PullRequests: query.Repository.PullRequests.Nodes,
	}

	return result, nil
}

func (p *pullRequestsServiceImpl) Approve(ctx context.Context, owner, repo string, number int, body string) (*PullRequestReview, error) {
	pr, err := p.Get(ctx, owner, repo, number)
	if err != nil {
		return nil, err
	}

	var mutation struct {
		AddPullRequestReview struct {
			PullRequestReview PullRequestReview
		} `graphql:"addPullRequestReview(input: $input)"`
	}

	input := githubv4.AddPullRequestReviewInput{
		PullRequestID: pr.NodeID,
		Body:          pointer.Pointer(githubv4.String(body)),
		Event:         pointer.Pointer(githubv4.PullRequestReviewEventApprove),
	}

	err = p.cliv4.Mutate(ctx, &mutation, input, nil)
	if err != nil {
		return nil, err
	}
	return &mutation.AddPullRequestReview.PullRequestReview, nil
}

func (p *pullRequestsServiceImpl) Merge(ctx context.Context, owner string, repo string, number int, method githubv4.PullRequestMergeMethod) (*PullRequest, error) {
	pr, err := p.Get(ctx, owner, repo, number)
	if err != nil {
		return nil, err
	}

	if pr.State == githubv4.PullRequestStateMerged {
		return pr, nil
	}

	var mutation struct {
		MergePullRequest struct {
			PullRequest PullRequest
		} `graphql:"mergePullRequest(input: $input)"`
	}

	input := githubv4.MergePullRequestInput{
		PullRequestID: pr.NodeID,
		MergeMethod:   &method,
	}

	err = p.cliv4.Mutate(ctx, &mutation, input, nil)
	if err != nil {
		return nil, err
	}

	return &mutation.MergePullRequest.PullRequest, nil
}

func (p *pullRequestsServiceImpl) AddComment(ctx context.Context, owner, repo string, number int, body string) (*Comment, error) {
	pr, err := p.Get(ctx, owner, repo, number)
	if err != nil {
		return nil, err
	}

	var mutation struct {
		AddComment struct {
			CommentEdge struct {
				Node Comment
			}
		} `graphql:"addComment(input: $input)"`
	}

	input := githubv4.AddCommentInput{
		SubjectID: pr.NodeID,
		Body:      githubv4.String(body),
	}

	err = p.cliv4.Mutate(ctx, &mutation, input, nil)
	if err != nil {
		return nil, err
	}

	return &mutation.AddComment.CommentEdge.Node, nil
}

// func (p *pullRequestsServiceImpl) MinimizeLastComment(ctx context.Context, owner string, repo string, number int) error {
// 	var query struct {
// 		Repository struct {
// 			PullRequest struct {
// 				Comments struct {
// 					Nodes    []Comment
// 					PageInfo PagerResult
// 				} `graphql:"comments(first: 10, after: $cursor, orderBy: { field: UPDATED_AT, direction: DESC })"`
// 			} `graphql:"pullRequest(number: $prNumber)"`
// 		} `graphql:"repository(owner: $owner, name: $repoName)"`
// 	}
// 	variables := map[string]interface{}{
// 		"owner":    githubv4.String(owner),
// 		"repoName": githubv4.String(repo),
// 		"prNumber": githubv4.Int(number),
// 		"cursor":   (*githubv4.String)(nil),
// 	}

// 	var mutation struct {
// 		MinimizeComment struct {
// 			// fields returned
// 			ClientMutationID string
// 		} `graphql:"minimizeComment(input: $input)"`
// 	}

// 	for {
// 		err := p.cliv4.Query(ctx, &query, variables)
// 		if err != nil {
// 			return nil
// 		}

// 		// minimize last comment of author if found
// 		for _, comment := range query.Repository.PullRequest.Comments.Nodes {
// 			// FIXME: not work now
// 			if !container.Exist(comment.Author.Login, clientNames) {
// 				continue
// 			}
// 			if comment.IsMinimized {
// 				return nil
// 			}
// 			input := githubv4.MinimizeCommentInput{
// 				SubjectID:  comment.NodeID,
// 				Classifier: githubv4.ReportedContentClassifiersOutdated,
// 			}
// 			return p.cliv4.Mutate(ctx, &mutation, input, nil)
// 		}

// 		// next page
// 		if !query.Repository.PullRequest.Comments.PageInfo.HasNextPage {
// 			break
// 		}
// 		variables["cursor"] = githubv4.NewString(query.Repository.PullRequest.Comments.PagerResult.EndCursor)
// 	}

// 	return nil
// }
