package githubcli

import (
	context "context"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/devops/pkg/infra/log"

	"github.com/google/go-github/v54/github"
	"github.com/shurcooL/githubv4"
)

type ListReleaseOption struct {
	Pager
}

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

type ListReleaseResult struct {
	Page     PagerResult
	Releases []Release
}

type ReleaseService interface {
	// Get returns a release of a repository.
	Get(ctx context.Context, owner string, repo string, tag string) (*Release, error)
	// List returns releases of a repository.
	List(ctx context.Context, owner string, repo string, opt ListReleaseOption) (*ListReleaseResult, error)
	// Diff returns all pull requests between target and prev tag.
	//
	// NOTE: It parses release notes to get pull requests, so that the result miss some field and may be inaccurate.
	Diff(ctx context.Context, owner string, repo string, targetTag, prevTag string) ([]PullRequest, error)
}

var _ ReleaseService = (*releaseServiceImpl)(nil)

type releaseServiceImpl GithubClient

func (p *releaseServiceImpl) Get(ctx context.Context, owner string, repo string, tag string) (*Release, error) {
	var query struct {
		Repository struct {
			Release Release `graphql:"release(tagName: $tagName)"`
		} `graphql:"repository(owner: $owner, name: $repoName)"`
	}

	variables := map[string]any{
		"owner":    githubv4.String(owner),
		"repoName": githubv4.String(repo),
		"tagName":  githubv4.String(tag),
	}

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

	return &query.Repository.Release, nil
}

func (p *releaseServiceImpl) List(ctx context.Context, owner string, repo string, opt ListReleaseOption) (*ListReleaseResult, error) {
	opt = opt.Normalize()

	var query struct {
		Repository struct {
			Releases struct {
				Nodes    []Release
				PageInfo PagerResult
			} `graphql:"releases(first: $perPage, after: $cursor, orderBy: { field: CREATED_AT, direction: DESC })"`
		} `graphql:"repository(owner: $owner, name: $repoName)"`
	}

	variables := map[string]any{
		"owner":    githubv4.String(owner),
		"repoName": githubv4.String(repo),
		"perPage":  githubv4.Int(opt.PerPage),
		"cursor":   (*githubv4.String)(nil),
	}
	if opt.Cursor != "" {
		variables["cursor"] = githubv4.NewString(githubv4.String(opt.Cursor))
	}

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

	result := &ListReleaseResult{
		Page:     query.Repository.Releases.PageInfo,
		Releases: query.Repository.Releases.Nodes,
	}

	return result, nil
}

// A example of release notes:
//
// ### What's Changed
// * secondary confirm: another way to show and change some cluster's gito… by @jinshayumi in #103
// * fix: tenant and cluster id type by @iguoyr in #137
// ...
//
// NOTE: It parses release notes to get pull requests, so that the result miss some field and may be inaccurate.
func (p *releaseServiceImpl) Diff(ctx context.Context, owner string, repo string, targetTag, prevTag string) ([]PullRequest, error) {
	notes, _, err := p.cliv3.Repositories.GenerateReleaseNotes(ctx, owner, repo, &github.GenerateNotesOptions{
		TagName:         targetTag,
		TargetCommitish: &targetTag,
		PreviousTagName: &prevTag,
	})
	if err != nil {
		return nil, err
	}
	lines := strings.Split(notes.Body, "\n")

	results := make([]PullRequest, 0, len(lines))

	for _, line := range lines {
		match := regexParseNotes.FindStringSubmatch(line)
		if len(match) != 6 {
			continue
		}

		title := match[1]
		user := match[2]
		ownerInLine := match[3]
		repoInLine := match[4]
		prNumberStr := match[5]

		if ownerInLine != owner || repoInLine != repo {
			log.Logger.Error(err, "invalid line in release notes", line)
			continue
		}

		prNr, err := strconv.ParseInt(prNumberStr, 10, 64)
		if err != nil {
			log.Logger.Error(err, "invalid line in release notes", line)
			continue
		}

		results = append(results, PullRequest{
			Title:  title,
			Number: prNr,
			Author: Author{
				Login: user,
			},
			State: githubv4.PullRequestStateMerged,
			Repository: Repository{
				Owner:         Author{Login: owner},
				Name:          repo,
				NameWithOwner: fmt.Sprintf("%s/%s", owner, repo),
			},
		})
	}

	return results, nil
}
