// Copyright 2023 Harness, 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.

package command

import (
	"io"
	"strconv"
	"strings"
	"time"
)

type CmdOptionFunc func(c *Command)

// WithAction set the action of the Git command, e.g. "set-url" in `git remote set-url`.
func WithAction(action string) CmdOptionFunc {
	return func(c *Command) {
		c.Action = action
	}
}

// WithFlag set optional flags to pass before positional arguments.
func WithFlag(flags ...string) CmdOptionFunc {
	return func(c *Command) {
		c.Flags = append(c.Flags, flags...)
	}
}

// WithArg add arguments that shall be passed after all flags.
func WithArg(args ...string) CmdOptionFunc {
	return func(c *Command) {
		c.Args = append(c.Args, args...)
	}
}

// WithPostSepArg set arguments that shall be passed as positional arguments after the `--`.
func WithPostSepArg(args ...string) CmdOptionFunc {
	return func(c *Command) {
		c.PostSepArgs = append(c.PostSepArgs, args...)
	}
}

// WithEnv sets environment variable using key value pair
// for example: WithEnv("GIT_TRACE", "true").
func WithEnv(keyValPairs ...string) CmdOptionFunc {
	return func(c *Command) {
		for i := 0; i < len(keyValPairs); i += 2 {
			k, v := keyValPairs[i], keyValPairs[i+1]
			c.Envs[k] = v
		}
	}
}

// WithCommitter sets given committer to the command.
func WithCommitter(name, email string) CmdOptionFunc {
	return func(c *Command) {
		c.Envs[GitCommitterName] = name
		c.Envs[GitCommitterEmail] = email
	}
}

// WithCommitterAndDate sets given committer and date to the command.
func WithCommitterAndDate(name, email string, date time.Time) CmdOptionFunc {
	return func(c *Command) {
		c.Envs[GitCommitterName] = name
		c.Envs[GitCommitterEmail] = email
		c.Envs[GitCommitterDate] = date.Format(time.RFC3339)
	}
}

// WithAuthor sets given author to the command.
func WithAuthor(name, email string) CmdOptionFunc {
	return func(c *Command) {
		c.Envs[GitAuthorName] = name
		c.Envs[GitAuthorEmail] = email
	}
}

// WithAuthorAndDate sets given author and date to the command.
func WithAuthorAndDate(name, email string, date time.Time) CmdOptionFunc {
	return func(c *Command) {
		c.Envs[GitAuthorName] = name
		c.Envs[GitAuthorEmail] = email
		c.Envs[GitAuthorDate] = date.Format(time.RFC3339)
	}
}

// WithConfig function sets key and value for config command.
func WithConfig(key, value string) CmdOptionFunc {
	return func(c *Command) {
		c.Envs["GIT_CONFIG_KEY_"+strconv.Itoa(c.configEnvCounter)] = key
		c.Envs["GIT_CONFIG_VALUE_"+strconv.Itoa(c.configEnvCounter)] = value
		c.configEnvCounter++
		c.Envs["GIT_CONFIG_COUNT"] = strconv.Itoa(c.configEnvCounter)
	}
}

// WithAlternateObjectDirs function sets alternates directories for object access.
func WithAlternateObjectDirs(dirs ...string) CmdOptionFunc {
	return func(c *Command) {
		if len(dirs) > 0 {
			c.Envs[GitAlternateObjectDirs] = strings.Join(dirs, ":")
		}
	}
}

// RunOption contains option for running a command.
type RunOption struct {
	// Dir is location of repo.
	Dir string
	// Stdin is the input to the command.
	Stdin io.Reader
	// Stdout is the outputs from the command.
	Stdout io.Writer
	// Stderr is the error output from the command.
	Stderr io.Writer
	// Envs is environments slice containing (final) immutable
	// environment pair "ENV=value"
	Envs []string
}

type RunOptionFunc func(option *RunOption)

// WithDir set directory RunOption.Dir, this is repository dir
// where git command should be running.
func WithDir(dir string) RunOptionFunc {
	return func(option *RunOption) {
		option.Dir = dir
	}
}

// WithStdin set RunOption.Stdin reader.
func WithStdin(stdin io.Reader) RunOptionFunc {
	return func(option *RunOption) {
		option.Stdin = stdin
	}
}

// WithStdout set RunOption.Stdout writer.
func WithStdout(stdout io.Writer) RunOptionFunc {
	return func(option *RunOption) {
		option.Stdout = stdout
	}
}

// WithStderr set RunOption.Stderr writer.
func WithStderr(stderr io.Writer) RunOptionFunc {
	return func(option *RunOption) {
		option.Stderr = stderr
	}
}

// WithEnvs sets immutable values as slice, it is always added
// et the end of env slice.
func WithEnvs(envs ...string) RunOptionFunc {
	return func(option *RunOption) {
		option.Envs = append(option.Envs, envs...)
	}
}