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

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

	"coopcloud.tech/abra/pkg/i18n"
	composeGoCli "github.com/compose-spec/compose-go/v2/cli"
	composeGoTypes "github.com/compose-spec/compose-go/v2/types"
	"github.com/docker/cli/cli/compose/loader"
	"github.com/docker/cli/cli/compose/schema"
	composetypes "github.com/docker/cli/cli/compose/types"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type LoadConf struct {
	ComposeFiles []string
	AppEnv       map[string]string
}

func LoadCompose(conf LoadConf) (*composeGoTypes.Project, error) {
	var project *composeGoTypes.Project

	// NOTE(d1): silence compose-go internal logger
	logrus.SetOutput(ioutil.Discard)

	var projectOptions *composeGoCli.ProjectOptions
	if len(conf.ComposeFiles) == 0 {
		return project, errors.New(i18n.G("LoadCompose: provide compose files"))
	}

	if len(conf.AppEnv) == 0 {
		var err error
		projectOptions, err = composeGoCli.NewProjectOptions(
			conf.ComposeFiles,
			composeGoCli.WithInterpolation(false),
		)
		if err != nil {
			return project, err
		}
	} else {
		var env []string
		for k, v := range conf.AppEnv {
			env = append(env, fmt.Sprintf("%s=%s", k, v))
		}

		var err error
		projectOptions, err = composeGoCli.NewProjectOptions(
			conf.ComposeFiles,
			composeGoCli.WithEnv(env),
		)
		if err != nil {
			return project, err
		}
	}

	project, err := projectOptions.LoadProject(context.Background())
	if err != nil {
		return project, err
	}

	return project, 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, errors.New(i18n.G("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, fmt.Errorf("%s: %s", filename, err)
	}

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