package github

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

	piperhttp "github.com/SAP/jenkins-library/pkg/http"
	"github.com/SAP/jenkins-library/pkg/log"
	"github.com/google/go-github/v45/github"
	"github.com/pkg/errors"
	"golang.org/x/oauth2"
)

type githubCreateIssueService interface {
	Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error)
}

type githubSearchIssuesService interface {
	Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error)
}

type githubCreateCommentService interface {
	CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
}

// CreateIssueOptions to configure the creation
type CreateIssueOptions struct {
	APIURL         string   `json:"apiUrl,omitempty"`
	Assignees      []string `json:"assignees,omitempty"`
	Body           []byte   `json:"body,omitempty"`
	Owner          string   `json:"owner,omitempty"`
	Repository     string   `json:"repository,omitempty"`
	Title          string   `json:"title,omitempty"`
	UpdateExisting bool     `json:"updateExisting,omitempty"`
	Token          string   `json:"token,omitempty"`
	TrustedCerts   []string `json:"trustedCerts,omitempty"`
}

// NewClient creates a new GitHub client using an OAuth token for authentication
func NewClient(token, apiURL, uploadURL string, trustedCerts []string) (context.Context, *github.Client, error) {
	httpClient := piperhttp.Client{}
	httpClient.SetOptions(piperhttp.ClientOptions{
		TrustedCerts:             trustedCerts,
		DoLogRequestBodyOnDebug:  true,
		DoLogResponseBodyOnDebug: true,
	})
	stdClient := httpClient.StandardClient()
	ctx := context.WithValue(context.Background(), oauth2.HTTPClient, stdClient)
	ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token, TokenType: "Bearer"})
	tc := oauth2.NewClient(ctx, ts)

	if !strings.HasSuffix(apiURL, "/") {
		apiURL += "/"
	}
	baseURL, err := url.Parse(apiURL)
	if err != nil {
		return ctx, nil, err
	}

	if !strings.HasSuffix(uploadURL, "/") {
		uploadURL += "/"
	}
	uploadTargetURL, err := url.Parse(uploadURL)
	if err != nil {
		return ctx, nil, err
	}

	client := github.NewClient(tc)

	client.BaseURL = baseURL
	client.UploadURL = uploadTargetURL
	return ctx, client, nil
}

func CreateIssue(ghCreateIssueOptions *CreateIssueOptions) error {
	ctx, client, err := NewClient(ghCreateIssueOptions.Token, ghCreateIssueOptions.APIURL, "", ghCreateIssueOptions.TrustedCerts)
	if err != nil {
		return errors.Wrap(err, "failed to get GitHub client")
	}
	return createIssueLocal(ctx, ghCreateIssueOptions, client.Issues, client.Search, client.Issues)
}

func createIssueLocal(ctx context.Context, ghCreateIssueOptions *CreateIssueOptions, ghCreateIssueService githubCreateIssueService, ghSearchIssuesService githubSearchIssuesService, ghCreateCommentService githubCreateCommentService) error {
	issue := github.IssueRequest{
		Title: &ghCreateIssueOptions.Title,
	}
	var bodyString string
	if len(ghCreateIssueOptions.Body) > 0 {
		bodyString = string(ghCreateIssueOptions.Body)
	} else {
		bodyString = ""
	}
	issue.Body = &bodyString
	if len(ghCreateIssueOptions.Assignees) > 0 {
		issue.Assignees = &ghCreateIssueOptions.Assignees
	} else {
		issue.Assignees = &[]string{}
	}

	var existingIssue *github.Issue = nil

	if ghCreateIssueOptions.UpdateExisting {
		queryString := fmt.Sprintf("is:open is:issue repo:%v/%v in:title %v", ghCreateIssueOptions.Owner, ghCreateIssueOptions.Repository, ghCreateIssueOptions.Title)
		searchResult, resp, err := ghSearchIssuesService.Issues(ctx, queryString, nil)
		if err != nil {
			if resp != nil {
				log.Entry().Errorf("GitHub search issue returned response code %v", resp.Status)
			}
			return errors.Wrap(err, "error occurred when looking for existing issue")
		} else {
			for _, value := range searchResult.Issues {
				if value != nil && *value.Title == ghCreateIssueOptions.Title {
					existingIssue = value
				}
			}
		}

		if existingIssue != nil {
			comment := &github.IssueComment{Body: issue.Body}
			_, resp, err := ghCreateCommentService.CreateComment(ctx, ghCreateIssueOptions.Owner, ghCreateIssueOptions.Repository, *existingIssue.Number, comment)
			if err != nil {
				if resp != nil {
					log.Entry().Errorf("GitHub create comment returned response code %v", resp.Status)
				}
				return errors.Wrap(err, "error occurred when adding comment to existing issue")
			}
		}
	}

	if existingIssue == nil {
		newIssue, resp, err := ghCreateIssueService.Create(ctx, ghCreateIssueOptions.Owner, ghCreateIssueOptions.Repository, &issue)
		if err != nil {
			if resp != nil {
				log.Entry().Errorf("GitHub create issue returned response code %v", resp.Status)
			}
			return errors.Wrap(err, "error occurred when creating issue")
		}
		log.Entry().Debugf("New issue created: %v", newIssue)
	}

	return nil
}
