// 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"

	gitlab "gitlab.com/gitlab-org/api/client-go"
)

type GitlabClient struct {
	client    *gitlab.Client
	username  string
	ownerName string
	repoName  string
	token     string
	projectId int
	// A mapping from branch names to MR IDs
	branchToMR map[string]int
}

func NewGitlabClient() *GitlabClient {

	gitlabUsername := os.Getenv("ATLANTIS_GITLAB_USER")
	if gitlabUsername == "" {
		log.Fatalf("ATLANTIS_GITLAB_USER cannot be empty")
	}
	gitlabToken := os.Getenv("ATLANTIS_GITLAB_TOKEN")
	if gitlabToken == "" {
		log.Fatalf("ATLANTIS_GITLAB_TOKEN cannot be empty")
	}
	ownerName := os.Getenv("GITLAB_REPO_OWNER_NAME")
	if ownerName == "" {
		ownerName = "runatlantis"
	}
	repoName := os.Getenv("GITLAB_REPO_NAME")
	if repoName == "" {
		repoName = "atlantis-tests"
	}

	gitlabClient, err := gitlab.NewClient(gitlabToken)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	project, _, err := gitlabClient.Projects.GetProject(fmt.Sprintf("%s/%s", ownerName, repoName), &gitlab.GetProjectOptions{})
	if err != nil {
		log.Fatalf("Failed to find project: %v", err)
	}

	return &GitlabClient{
		client:     gitlabClient,
		username:   gitlabUsername,
		ownerName:  ownerName,
		repoName:   repoName,
		token:      gitlabToken,
		projectId:  project.ID,
		branchToMR: make(map[string]int),
	}

}

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

	repoURL := fmt.Sprintf("https://%s:%s@gitlab.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 GitlabClient) CreateAtlantisWebhook(ctx context.Context, hookURL string) (int64, error) {
	hook, _, err := g.client.Projects.AddProjectHook(g.projectId, &gitlab.AddProjectHookOptions{
		URL:                 &hookURL,
		IssuesEvents:        gitlab.Ptr(true),
		MergeRequestsEvents: gitlab.Ptr(true),
		PushEvents:          gitlab.Ptr(true),
	})
	if err != nil {
		return 0, err
	}
	log.Printf("created webhook for %s", hook.URL)
	return int64(hook.ID), err
}

func (g GitlabClient) DeleteAtlantisHook(ctx context.Context, hookID int64) error {
	_, err := g.client.Projects.DeleteProjectHook(g.projectId, int(hookID))
	if err != nil {
		return err
	}
	log.Printf("deleted webhook id %d", hookID)
	return nil
}

func (g GitlabClient) CreatePullRequest(ctx context.Context, title, branchName string) (string, int, error) {

	mr, _, err := g.client.MergeRequests.CreateMergeRequest(g.projectId, &gitlab.CreateMergeRequestOptions{
		Title:        gitlab.Ptr(title),
		SourceBranch: gitlab.Ptr(branchName),
		TargetBranch: gitlab.Ptr("main"),
	})
	if err != nil {
		return "", 0, fmt.Errorf("error while creating new pull request: %v", err)
	}
	g.branchToMR[branchName] = mr.IID
	return mr.WebURL, mr.IID, nil

}

func (g GitlabClient) GetAtlantisStatus(ctx context.Context, branchName string) (string, error) {

	pipelineInfos, _, err := g.client.MergeRequests.ListMergeRequestPipelines(g.projectId, g.branchToMR[branchName])
	if err != nil {
		return "", err
	}
	// Possible todo: determine which status in the pipeline we care about?
	if len(pipelineInfos) != 1 {
		return "", fmt.Errorf("unexpected pipelines: %d", len(pipelineInfos))
	}
	pipelineInfo := pipelineInfos[0]
	pipeline, _, err := g.client.Pipelines.GetPipeline(g.projectId, pipelineInfo.ID)
	if err != nil {
		return "", err
	}

	return pipeline.Status, nil
}

func (g GitlabClient) ClosePullRequest(ctx context.Context, pullRequestNumber int) error {
	// clean up
	_, _, err := g.client.MergeRequests.UpdateMergeRequest(g.projectId, pullRequestNumber, &gitlab.UpdateMergeRequestOptions{
		StateEvent: gitlab.Ptr("close"),
	})
	if err != nil {
		return fmt.Errorf("error while closing new pull request: %v", err)
	}
	return nil

}
func (g GitlabClient) DeleteBranch(ctx context.Context, branchName string) error {
	_, err := g.client.Branches.DeleteBranch(g.projectId, branchName)

	if err != nil {
		return fmt.Errorf("error while deleting branch %s: %v", branchName, err)
	}
	return nil

}

func (g GitlabClient) IsAtlantisInProgress(state string) bool {
	// From https://docs.gitlab.com/api/pipelines/
	// created, waiting_for_resource, preparing, pending, running, success, failed, canceled, skipped, manual, scheduled
	for _, s := range []string{"success", "failed", "canceled", "skipped"} {
		if state == s {
			return false
		}
	}
	return true
}

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