package git

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/Sirupsen/logrus"
)

// Commits returns a set of commits.
// If commitrange is a git still range 12345...54321, then it will be isolated set of commits.
// If commitrange is a single commit, all ancestor commits up through the hash provided.
func Commits(commitrange string) ([]CommitEntry, error) {
	cmdArgs := []string{"git", "log", prettyFormat + formatCommit, commitrange}
	if Verbose {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		return nil, err
	}
	commitHashes := strings.Split(strings.TrimSpace(string(output)), "\n")
	commits := make([]CommitEntry, len(commitHashes))
	for i, commitHash := range commitHashes {
		c, err := LogCommit(commitHash)
		if err != nil {
			return commits, err
		}
		commits[i] = *c
	}
	return commits, nil
}

// CommitEntry represents a single commit's information from `git`
type CommitEntry map[string]string

var (
	// Verbose output of commands run
	Verbose = false

	prettyFormat         = `--pretty=format:`
	formatSubject        = `%s`
	formatBody           = `%b`
	formatCommit         = `%H`
	formatAuthorName     = `%aN`
	formatAuthorEmail    = `%aE`
	formatCommitterName  = `%cN`
	formatCommitterEmail = `%cE`
	formatSigner         = `%GS`
	formatCommitNotes    = `%N`
	formatMap            = `{"commit": "%H", "abbreviated_commit": "%h", "tree": "%T", "abbreviated_tree": "%t", "parent": "%P", "abbreviated_parent": "%p", "refs": "%D", "encoding": "%e", "sanitized_subject_line": "%f", "verification_flag": "%G?", "signer_key": "%GK", "author_date": "%aD" , "committer_date": "%cD" }`
)

// LogCommit assembles the full information on a commit from its commit hash
func LogCommit(commit string) (*CommitEntry, error) {
	buf := bytes.NewBuffer([]byte{})
	cmdArgs := []string{"git", "log", "-1", prettyFormat + formatMap, commit}
	if Verbose {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
	cmd.Stdout = buf
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Println(strings.Join(cmd.Args, " "))
		return nil, err
	}
	c := CommitEntry{}
	output := buf.Bytes()
	if err := json.Unmarshal(output, &c); err != nil {
		fmt.Println(string(output))
		return nil, err
	}

	// any user provided fields can't be sanitized for the mock-json marshal above
	for k, v := range map[string]string{
		"subject":         formatSubject,
		"body":            formatBody,
		"author_name":     formatAuthorName,
		"author_email":    formatAuthorEmail,
		"committer_name":  formatCommitterName,
		"committer_email": formatCommitterEmail,
		"commit_notes":    formatCommitNotes,
		"signer":          formatSigner,
	} {
		output, err := exec.Command("git", "log", "-1", prettyFormat+v, commit).Output()
		if err != nil {
			return nil, err
		}
		c[k] = strings.TrimSpace(string(output))
	}

	return &c, nil
}

// FetchHeadCommit returns the hash of FETCH_HEAD
func FetchHeadCommit() (string, error) {
	cmdArgs := []string{"git", "rev-parse", "--verify", "FETCH_HEAD"}
	if Verbose {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}

// HeadCommit returns the hash of HEAD
func HeadCommit() (string, error) {
	cmdArgs := []string{"git", "rev-parse", "--verify", "HEAD"}
	if Verbose {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}