// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package cmd

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"google.golang.org/grpc"

	"github.com/gitpod-io/gitpod/gitpod-cli/pkg/theialib"
	supervisor "github.com/gitpod-io/gitpod/supervisor/api"
)

var credentialHelper = &cobra.Command{
	Use:    "credential-helper get",
	Short:  "Gitpod Credential Helper for Git",
	Long:   "Supports reading of credentials per host.",
	Args:   cobra.MinimumNArgs(1),
	Hidden: true,
	Run: func(cmd *cobra.Command, args []string) {
		action := args[0]
		log.SetOutput(io.Discard)
		f, err := os.OpenFile(os.TempDir()+"/gitpod-git-credential-helper.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
		if err == nil {
			defer f.Close()
			log.SetOutput(f)
		}
		if action != "get" {
			return
		}

		var user, token string
		defer func() {
			// token was not found, thus we return just a dummy to satisfy the git protocol
			if user == "" {
				user = "oauth2"
			}
			if token == "" {
				token = "no"
			}
			fmt.Printf("username=%s\npassword=%s\n", user, token)
		}()

		repoURL, gitCommand := parsePstree()
		host := parseHostFromStdin()
		if len(host) == 0 {
			log.Println("'host' is missing")
		}

		if isTheiaIDE() {
			service, err := theialib.NewServiceFromEnv()
			if err != nil {
				log.WithError(err).Print("cannot connect to Theia")
				return
			}
			if action == "get" {
				resp, err := service.GetGitToken(theialib.GetGitTokenRequest{
					Command: gitCommand,
					Host:    host,
					RepoURL: repoURL,
				})
				if err != nil {
					log.WithError(err).Print("cannot get token")
					return
				}
				user = resp.User
				token = resp.Token
			}
			return
		}
		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
		defer cancel()
		supervisorAddr := os.Getenv("SUPERVISOR_ADDR")
		if supervisorAddr == "" {
			supervisorAddr = "localhost:22999"
		}
		supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure())
		if err != nil {
			log.WithError(err).Print("error connecting to supervisor")
			return
		}
		resp, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{
			Host: host,
			Kind: "git",
		})
		if err != nil {
			log.WithError(err).Print("error getting token from supervisior")
			return
		}

		validator := exec.Command("/proc/self/exe", "git-token-validator",
			"--user", resp.User, "--token", resp.Token, "--scopes", strings.Join(resp.Scope, ","),
			"--host", host, "--repoURL", repoURL, "--gitCommand", gitCommand)
		err = validator.Start()
		if err != nil {
			log.WithError(err).Print("error spawning validator")
			return
		}
		err = validator.Process.Release()
		if err != nil {
			log.WithError(err).Print("error releasing validator")
			return
		}
		user = resp.User
		token = resp.Token
	},
}

func isTheiaIDE() bool {
	stat, err := os.Stat("/theia")
	return !errors.Is(os.ErrNotExist, err) && stat != nil && stat.IsDir()
}

func parseHostFromStdin() string {
	host := ""
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if len(line) > 0 {
			tuple := strings.Split(line, "=")
			if len(tuple) == 2 {
				if strings.TrimSpace(tuple[0]) == "host" {
					host = strings.TrimSpace(tuple[1])
				}
			}
		}
	}
	if err := scanner.Err(); err != nil {
		log.Println(err)
	}
	return host
}

func logDebug(v ...interface{}) {
	if os.Getenv("CREDENTIAL_HELPER_DEBUG_LOG") == "true" {
		log.Println(v...)
	}
}

func parsePstree() (string, string) {
	url := ""
	gitCommand := ""
	cmd := exec.Command("pstree", "-sa", strconv.Itoa(os.Getpid()))
	msg, err := cmd.CombinedOutput()
	if err != nil {
		log.Println(err)
	} else {
		pstree := string(msg)
		logDebug("debug> pstree: ")
		logDebug(pstree)

		// git command
		re := regexp.MustCompile(`git(,\d+\s+|\s+)(push|clone|fetch|pull|diff)`)
		match := re.FindStringSubmatch(pstree)
		if len(match) == 3 {
			gitCommand = match[2]
		}

		// url
		re = regexp.MustCompile(`origin\s*(https:.*\.git)\n`)
		match = re.FindStringSubmatch(pstree)
		if len(match) == 2 {
			url = match[1]
		}
	}
	return url, gitCommand
}

func init() {
	rootCmd.AddCommand(credentialHelper)
}
