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

type E2ETester struct {
	vcsClient    VCSClient
	hookID       int64
	cloneDirRoot string
	projectType  Project
}

type E2EResult struct {
	projectType    string
	pullRequestURL string
	testResult     string
}

var testFileData = `
resource "null_resource" "hello" {
}
`

// nolint: gosec
func (t *E2ETester) Start(ctx context.Context) (*E2EResult, error) {
	cloneDir := fmt.Sprintf("%s/%s-test", t.cloneDirRoot, t.projectType.Name)
	branchName := fmt.Sprintf("%s-%s", t.projectType.Name, time.Now().Format("20060102150405"))
	testFileName := fmt.Sprintf("%s.tf", t.projectType.Name)
	e2eResult := &E2EResult{}
	e2eResult.projectType = t.projectType.Name

	// create the directory and parents if necessary
	log.Printf("creating dir %q", cloneDir)
	if err := os.MkdirAll(cloneDir, 0700); err != nil {
		return e2eResult, fmt.Errorf("failed to create dir %q prior to cloning, attempting to continue: %v", cloneDir, err)
	}

	err := t.vcsClient.Clone(cloneDir)
	if err != nil {
		return e2eResult, err
	}

	// checkout a new branch for the project
	log.Printf("checking out branch %q", branchName)
	checkoutCmd := exec.Command("git", "checkout", "-b", branchName)
	checkoutCmd.Dir = cloneDir
	if output, err := checkoutCmd.CombinedOutput(); err != nil {
		return e2eResult, fmt.Errorf("failed to git checkout branch %q: %v: %s", branchName, err, string(output))
	}

	// write a file for running the tests
	randomData := []byte(testFileData)
	filePath := fmt.Sprintf("%s/%s/%s", cloneDir, t.projectType.Name, testFileName)
	log.Printf("creating file to commit %q", filePath)
	err = os.WriteFile(filePath, randomData, 0644)
	if err != nil {
		return e2eResult, fmt.Errorf("couldn't write file %s: %v", filePath, err)
	}

	// add the file
	log.Printf("git add file %q", filePath)
	addCmd := exec.Command("git", "add", filePath)
	addCmd.Dir = cloneDir
	if output, err := addCmd.CombinedOutput(); err != nil {
		return e2eResult, fmt.Errorf("failed to git add file %q: %v: %s", filePath, err, string(output))
	}

	// commit the file
	log.Printf("git commit file %q", filePath)
	commitCmd := exec.Command("git", "commit", "-am", "test commit")
	commitCmd.Dir = cloneDir
	if output, err := commitCmd.CombinedOutput(); err != nil {
		return e2eResult, fmt.Errorf("failed to run git commit in %q: %v: %v", cloneDir, err, string(output))
	}

	// push the branch to remote
	log.Printf("git push branch %q", branchName)
	pushCmd := exec.Command("git", "push", "origin", branchName)
	pushCmd.Dir = cloneDir
	if output, err := pushCmd.CombinedOutput(); err != nil {
		return e2eResult, fmt.Errorf("failed to git push branch %q: %v: %s", branchName, err, string(output))
	}

	// create a new pr
	title := fmt.Sprintf("This is a test pull request for atlantis e2e test for %s project type", t.projectType.Name)
	url, pullId, err := t.vcsClient.CreatePullRequest(ctx, title, branchName)

	if err != nil {
		return e2eResult, err
	}

	// set pull request url
	e2eResult.pullRequestURL = url

	log.Printf("created pull request %s", url)

	// defer closing pull request and delete remote branch
	defer func() {
		err := cleanUp(ctx, t, pullId, branchName)
		if err != nil {
			log.Printf("Failed to cleanup: %v", err)
		}
	}()

	// wait for atlantis to respond to webhook and autoplan.
	time.Sleep(2 * time.Second)

	state := "not started"
	// waiting for atlantis run and finish
	maxLoops := 20
	i := 0
	for ; i < maxLoops && t.vcsClient.IsAtlantisInProgress(state); i++ {
		time.Sleep(2 * time.Second)
		state, _ = t.vcsClient.GetAtlantisStatus(ctx, branchName)
		if state == "" {
			log.Println("atlantis run hasn't started")
			continue
		}
		log.Printf("atlantis run is in %s state", state)
	}
	if i == maxLoops {
		state = "timed out"
	}

	log.Printf("atlantis run finished with status %q", state)
	e2eResult.testResult = state
	// check if atlantis run was a success
	if !t.vcsClient.DidAtlantisSucceed(state) {
		return e2eResult, fmt.Errorf("atlantis run project type %q failed with %q status", t.projectType.Name, state)
	}

	return e2eResult, nil
}

func cleanUp(ctx context.Context, t *E2ETester, pullRequestNumber int, branchName string) error {
	// clean up
	err := t.vcsClient.ClosePullRequest(ctx, pullRequestNumber)
	if err != nil {
		return err
	}
	log.Printf("closed pull request %d", pullRequestNumber)

	err = t.vcsClient.DeleteBranch(ctx, branchName)
	if err != nil {
		return fmt.Errorf("error while deleting branch %s: %v", branchName, err)
	}
	log.Printf("deleted branch %s", branchName)

	return nil
}
