package stack // https://github.com/docker/cli/blob/master/cli/command/stack/loader/loader.go

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"sort"
	"strings"

	"coopcloud.tech/abra/pkg/log"
	"github.com/docker/cli/cli/compose/loader"
	"github.com/docker/cli/cli/compose/schema"
	composetypes "github.com/docker/cli/cli/compose/types"
)

// DontSkipValidation ensures validation is done for compose file loading
func DontSkipValidation(opts *loader.Options) {
	opts.SkipValidation = false
}

// SkipInterpolation skip interpolating environment variables.
func SkipInterpolation(opts *loader.Options) {
	opts.SkipInterpolation = true
}

// LoadComposefile parse the composefile specified in the cli and returns its Config and version.
func LoadComposefile(opts Deploy, appEnv map[string]string, options ...func(*loader.Options)) (*composetypes.Config, error) {
	configDetails, err := getConfigDetails(opts.Composefiles, appEnv)
	if err != nil {
		return nil, err
	}

	if options == nil {
		options = []func(*loader.Options){DontSkipValidation}
	}

	dicts := getDictsFrom(configDetails.ConfigFiles)
	config, err := loader.Load(configDetails, options...)
	if err != nil {
		if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
			return nil, fmt.Errorf("compose file contains unsupported options: %s",
				propertyWarnings(fpe.Properties))
		}
		return nil, err
	}

	recipeName, exists := appEnv["RECIPE"]
	if !exists {
		recipeName, _ = appEnv["TYPE"]
	}

	unsupportedProperties := loader.GetUnsupportedProperties(dicts...)
	if len(unsupportedProperties) > 0 {
		log.Warnf("%s: ignoring unsupported options: %s",
			recipeName, strings.Join(unsupportedProperties, ", "))
	}

	deprecatedProperties := loader.GetDeprecatedProperties(dicts...)
	if len(deprecatedProperties) > 0 {
		log.Warnf("%s: ignoring deprecated options: %s",
			recipeName, propertyWarnings(deprecatedProperties))
	}
	return config, nil
}

func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]interface{} {
	dicts := []map[string]interface{}{}

	for _, configFile := range configFiles {
		dicts = append(dicts, configFile.Config)
	}

	return dicts
}

func propertyWarnings(properties map[string]string) string {
	var msgs []string
	for name, description := range properties {
		msgs = append(msgs, fmt.Sprintf("%s: %s", name, description))
	}
	sort.Strings(msgs)
	return strings.Join(msgs, "\n\n")
}

func getConfigDetails(composefiles []string, appEnv map[string]string) (composetypes.ConfigDetails, error) {
	var details composetypes.ConfigDetails

	absPath, err := filepath.Abs(composefiles[0])
	if err != nil {
		return details, err
	}
	details.WorkingDir = filepath.Dir(absPath)

	details.ConfigFiles, err = loadConfigFiles(composefiles)
	if err != nil {
		return details, err
	}
	// Take the first file version (2 files can't have different version)
	details.Version = schema.Version(details.ConfigFiles[0].Config)
	details.Environment = appEnv
	return details, err
}

func buildEnvironment(env []string) (map[string]string, error) {
	result := make(map[string]string, len(env))
	for _, s := range env {
		// if value is empty, s is like "K=", not "K".
		if !strings.Contains(s, "=") {
			return result, fmt.Errorf("unexpected environment %q", s)
		}
		kv := strings.SplitN(s, "=", 2)
		result[kv[0]] = kv[1]
	}
	return result, nil
}

func loadConfigFiles(filenames []string) ([]composetypes.ConfigFile, error) {
	var configFiles []composetypes.ConfigFile

	for _, filename := range filenames {
		configFile, err := loadConfigFile(filename)
		if err != nil {
			return configFiles, err
		}
		configFiles = append(configFiles, *configFile)
	}

	return configFiles, nil
}

func loadConfigFile(filename string) (*composetypes.ConfigFile, error) {
	var bytes []byte
	var err error

	bytes, err = ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	config, err := loader.ParseYAML(bytes)
	if err != nil {
		return nil, err
	}

	return &composetypes.ConfigFile{
		Filename: filename,
		Config:   config,
	}, nil
}
