// Copyright 2017 HootSuite Media Inc.
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modified hereafter by contributors to runatlantis/atlantis.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/google/go-github/v71/github"
)

type GithubClient struct {
	client    *github.Client
	username  string
	ownerName string
	repoName  string
	token     string
}

func NewGithubClient() *GithubClient {

	githubUsername := os.Getenv("ATLANTIS_GH_USER")
	if githubUsername == "" {
		log.Fatalf("ATLANTIS_GH_USER cannot be empty")
	}
	githubToken := os.Getenv("ATLANTIS_GH_TOKEN")
	if githubToken == "" {
		log.Fatalf("ATLANTIS_GH_TOKEN cannot be empty")
	}
	ownerName := os.Getenv("GITHUB_REPO_OWNER_NAME")
	if ownerName == "" {
		ownerName = "runatlantis"
	}
	repoName := os.Getenv("GITHUB_REPO_NAME")
	if repoName == "" {
		repoName = "atlantis-tests"
	}

	// create github client
	tp := github.BasicAuthTransport{
		Username: strings.TrimSpace(githubUsername),
		Password: strings.TrimSpace(githubToken),
	}
	ghClient := github.NewClient(tp.Client())

	return &GithubClient{
		client:    ghClient,
		username:  githubUsername,
		ownerName: ownerName,
		repoName:  repoName,
		token:     githubToken,
	}

}

func (g GithubClient) Clone(cloneDir string) error {

	repoURL := fmt.Sprintf("https://%s:%s@github.com/%s/%s.git", g.username, g.token, g.ownerName, g.repoName)
	cloneCmd := exec.Command("git", "clone", repoURL, cloneDir)
	// git clone the repo
	log.Printf("git cloning into %q", cloneDir)
	if output, err := cloneCmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to clone repository: %v: %s", err, string(output))
	}
	return nil
}

func (g GithubClient) CreateAtlantisWebhook(ctx context.Context, hookURL string) (int64, error) {
	contentType := "json"
	hookConfig := &github.HookConfig{
		ContentType: &contentType,
		URL:         &hookURL,
	}
	// create atlantis hook
	atlantisHook := &github.Hook{
		Events: []string{"issue_comment", "pull_request", "push"},
		Config: hookConfig,
		Active: github.Ptr(true),
	}

	hook, _, err := g.client.Repositories.CreateHook(ctx, g.ownerName, g.repoName, atlantisHook)
	if err != nil {
		return 0, err
	}
	log.Println(hook.GetURL())

	return hook.GetID(), nil
}

func (g GithubClient) DeleteAtlantisHook(ctx context.Context, hookID int64) error {
	_, err := g.client.Repositories.DeleteHook(ctx, g.ownerName, g.repoName, hookID)
	if err != nil {
		return err
	}
	log.Printf("deleted webhook id %d", hookID)

	return nil
}

func (g GithubClient) CreatePullRequest(ctx context.Context, title, branchName string) (string, int, error) {
	head := fmt.Sprintf("%s:%s", g.ownerName, branchName)
	body := ""
	base := "main"
	newPullRequest := &github.NewPullRequest{Title: &title, Head: &head, Body: &body, Base: &base}

	pull, _, err := g.client.PullRequests.Create(ctx, g.ownerName, g.repoName, newPullRequest)
	if err != nil {
		return "", 0, fmt.Errorf("error while creating new pull request: %v", err)
	}

	// set pull request url
	return pull.GetHTMLURL(), pull.GetNumber(), nil

}

func (g GithubClient) GetAtlantisStatus(ctx context.Context, branchName string) (string, error) {
	// check repo status
	combinedStatus, _, err := g.client.Repositories.GetCombinedStatus(ctx, g.ownerName, g.repoName, branchName, nil)
	if err != nil {
		return "", err
	}

	for _, status := range combinedStatus.Statuses {
		if status.GetContext() == "atlantis/plan" {
			return status.GetState(), nil
		}
	}

	return "", nil
}

func (g GithubClient) ClosePullRequest(ctx context.Context, pullRequestNumber int) error {
	// clean up
	_, _, err := g.client.PullRequests.Edit(ctx, g.ownerName, g.repoName, pullRequestNumber, &github.PullRequest{State: github.Ptr("closed")})
	if err != nil {
		return fmt.Errorf("error while closing new pull request: %v", err)
	}
	return nil

}
func (g GithubClient) DeleteBranch(ctx context.Context, branchName string) error {

	deleteBranchName := fmt.Sprintf("%s/%s", "heads", branchName)
	_, err := g.client.Git.DeleteRef(ctx, g.ownerName, g.repoName, deleteBranchName)
	if err != nil {
		return fmt.Errorf("error while deleting branch %s: %v", branchName, err)
	}
	return nil
}

func (g GithubClient) IsAtlantisInProgress(state string) bool {
	for _, s := range []string{"success", "error", "failure"} {
		if state == s {
			return false
		}
	}
	return true
}

func (g GithubClient) DidAtlantisSucceed(state string) bool {
	return state == "success"
}
