package githubcli

import (
	context "context"
	"time"

	"github.com/shurcooL/githubv4"
)

type ListLabelOption struct {
	Pager
}

func (o ListLabelOption) Normalize() ListLabelOption {
	if o.PerPage == 0 {
		o.PerPage = 20
	}
	if o.PerPage >= 50 {
		o.PerPage = 50
	}
	return o
}

type ListLabelResult struct {
	Page   PagerResult
	Labels []Label
}

type ListRepositoryOption struct {
	Pager

	PushedBefore *time.Time
}

func (o ListRepositoryOption) Normalize() ListRepositoryOption {
	if o.PerPage == 0 {
		o.PerPage = 20
	}
	if o.PerPage >= 50 {
		o.PerPage = 50
	}
	return o
}

type ListRepositoryResult struct {
	Page         PagerResult
	Repositories []Repository
}

type RepositoryService interface {
	// List repositories of an organization.
	List(ctx context.Context, org string, opt ListRepositoryOption) (*ListRepositoryResult, error)
	// Get a single repository.
	Get(ctx context.Context, owner string, repo string) (*Repository, error)
	// Get a single label.
	GetLabel(ctx context.Context, owner string, repo string, name string) (*Label, error)
	// List labels of a repository.
	ListLabels(ctx context.Context, owner string, repo string, opt ListLabelOption) (*ListLabelResult, error)
}

var _ RepositoryService = (*repositoryServiceImpl)(nil)

type repositoryServiceImpl GithubClient

func (p *repositoryServiceImpl) List(ctx context.Context, org string, opt ListRepositoryOption) (*ListRepositoryResult, error) {
	opt = opt.Normalize()

	var query struct {
		Organization struct {
			Repositories struct {
				Nodes    []Repository
				PageInfo PagerResult
			} `graphql:"repositories(first: $perPage, after: $cursor, orderBy: {field: PUSHED_AT, direction: DESC})"`
		} `graphql:"organization(login: $owner)"`
	}

	variables := map[string]any{
		"owner":   githubv4.String(org),
		"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
	}

	index := -1
	// find the index of the last repository that pushed before the given time
	if opt.PushedBefore != nil {
		for i := len(query.Organization.Repositories.Nodes) - 1; i >= 0; i-- {
			repo := query.Organization.Repositories.Nodes[i]
			if repo.PushedAt.Before(*opt.PushedBefore) {
				index = i
				break
			}
		}
	}

	results := &ListRepositoryResult{
		Page: PagerResult{
			EndCursor:   string(query.Organization.Repositories.PageInfo.EndCursor),
			HasNextPage: query.Organization.Repositories.PageInfo.HasNextPage,
		},
		Repositories: query.Organization.Repositories.Nodes,
	}
	if index >= 0 && index != len(query.Organization.Repositories.Nodes)-1 {
		results.Page.HasNextPage = false
		results.Repositories = query.Organization.Repositories.Nodes[:index+1]
	}

	return results, nil
}

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

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

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

	return &query.Repository, nil
}

func (p *repositoryServiceImpl) GetLabel(ctx context.Context, owner string, repo string, name string) (*Label, error) {
	var query struct {
		Repository struct {
			Label Label `graphql:"label(name: $name)"`
		} `graphql:"repository(owner: $owner, name: $repoName)"`
	}

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

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

	return &query.Repository.Label, nil
}

func (p *repositoryServiceImpl) ListLabels(ctx context.Context, owner string, repo string, opt ListLabelOption) (*ListLabelResult, error) {
	opt = opt.Normalize()

	var query struct {
		Repository struct {
			Labels struct {
				Nodes    []Label
				PageInfo PagerResult
			} `graphql:"labels(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 := &ListLabelResult{
		Page:   query.Repository.Labels.PageInfo,
		Labels: query.Repository.Labels.Nodes,
	}

	return result, nil
}
