package cmd

import (
	"encoding/json"
	"fmt"
	"io"
	"os"

	"github.com/SAP/jenkins-library/pkg/config"
	"github.com/SAP/jenkins-library/pkg/log"
	"github.com/SAP/jenkins-library/pkg/piperutils"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

type defaultsCommandOptions struct {
	output        string //output format of default configs, currently only YAML
	outputFile    string //if set: path to file where the output should be written to
	defaultsFiles []string
	useV1         bool
	openFile      func(s string, t map[string]string) (io.ReadCloser, error)
}

var defaultsOptions defaultsCommandOptions

type getDefaultsUtils interface {
	FileExists(filename string) (bool, error)
	DirExists(path string) (bool, error)
	FileWrite(path string, content []byte, perm os.FileMode) error
}

type getDefaultsUtilsBundle struct {
	*piperutils.Files
}

func newGetDefaultsUtilsUtils() getDefaultsUtils {
	utils := getDefaultsUtilsBundle{
		Files: &piperutils.Files{},
	}
	return &utils
}

// DefaultsCommand is the entry command for loading the configuration of a pipeline step
func DefaultsCommand() *cobra.Command {

	defaultsOptions.openFile = config.OpenPiperFile
	var createDefaultsCmd = &cobra.Command{
		Use:   "getDefaults",
		Short: "Retrieves multiple default configurations and outputs them embedded into a JSON object.",
		PreRun: func(cmd *cobra.Command, args []string) {
			path, _ := os.Getwd()
			fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
			log.RegisterHook(fatalHook)
			initStageName(false)
			GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens)
		},
		Run: func(cmd *cobra.Command, _ []string) {
			utils := newGetDefaultsUtilsUtils()
			_, err := generateDefaults(utils)
			if err != nil {
				log.SetErrorCategory(log.ErrorConfiguration)
				log.Entry().WithError(err).Fatal("failed to retrieve default configurations")
			}
		},
	}

	addDefaultsFlags(createDefaultsCmd)
	return createDefaultsCmd
}

func getDefaults() ([]map[string]string, error) {

	var yamlDefaults []map[string]string

	for _, f := range defaultsOptions.defaultsFiles {
		fc, err := defaultsOptions.openFile(f, GeneralConfig.GitHubAccessTokens)
		if err != nil {
			return yamlDefaults, errors.Wrapf(err, "defaults: retrieving defaults file failed: '%v'", f)
		}
		if err == nil {
			var yamlContent string

			if !defaultsOptions.useV1 {
				var c config.Config
				c.ReadConfig(fc)

				yamlContent, err = config.GetYAML(c)
				if err != nil {
					return yamlDefaults, errors.Wrapf(err, "defaults: could not marshal YAML default file: '%v", f)
				}
			} else {
				var rc config.RunConfigV1
				rc.StageConfigFile = fc
				rc.LoadConditionsV1()

				yamlContent, err = config.GetYAML(rc.PipelineConfig)
				if err != nil {
					return yamlDefaults, errors.Wrapf(err, "defaults: could not marshal YAML default file: '%v", f)
				}
			}

			yamlDefaults = append(yamlDefaults, map[string]string{"content": yamlContent, "filepath": f})
		}
	}

	return yamlDefaults, nil
}

func generateDefaults(utils getDefaultsUtils) ([]byte, error) {

	var jsonOutput []byte

	yamlDefaults, err := getDefaults()
	if err != nil {
		return jsonOutput, err
	}

	if len(yamlDefaults) > 1 {
		jsonOutput, err = json.Marshal(yamlDefaults)
	} else {
		jsonOutput, err = json.Marshal(yamlDefaults[0])
	}

	if err != nil {
		return jsonOutput, errors.Wrapf(err, "defaults: could not embed YAML defaults into JSON")
	}

	if len(defaultsOptions.outputFile) > 0 {
		err := utils.FileWrite(defaultsOptions.outputFile, []byte(jsonOutput), 0666)
		if err != nil {
			return jsonOutput, fmt.Errorf("failed to write output file %v: %w", configOptions.outputFile, err)
		}
		return jsonOutput, nil
	}
	fmt.Println(string(jsonOutput))

	return jsonOutput, nil
}

func addDefaultsFlags(cmd *cobra.Command) {

	cmd.Flags().StringVar(&defaultsOptions.output, "output", "yaml", "Defines the format of the configs embedded into a JSON object")
	cmd.Flags().StringVar(&defaultsOptions.outputFile, "outputFile", "", "Defines the output filename")
	cmd.Flags().StringArrayVar(&defaultsOptions.defaultsFiles, "defaultsFile", []string{}, "Defines the input defaults file(s)")
	cmd.Flags().BoolVar(&defaultsOptions.useV1, "useV1", false, "Input files are CRD-style stage configuration")
	cmd.MarkFlagRequired("defaultsFile")
}
