package compose

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

	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/upstream/stack"
	loader "coopcloud.tech/abra/pkg/upstream/stack"
	composetypes "github.com/docker/cli/cli/compose/types"
	"github.com/docker/distribution/reference"
	"github.com/sirupsen/logrus"
)

// UpdateTag updates an image tag in-place on file system local compose files.
func UpdateTag(pattern, image, tag, recipeName string) (bool, error) {
	composeFiles, err := filepath.Glob(pattern)
	if err != nil {
		return false, err
	}

	logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", "))

	for _, composeFile := range composeFiles {
		opts := stack.Deploy{Composefiles: []string{composeFile}}

		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
		sampleEnv, err := config.ReadEnv(envSamplePath)
		if err != nil {
			return false, err
		}

		compose, err := loader.LoadComposefile(opts, sampleEnv)
		if err != nil {
			return false, err
		}

		for _, service := range compose.Services {
			if service.Image == "" {
				continue // may be a compose.$optional.yml file
			}

			img, _ := reference.ParseNormalizedNamed(service.Image)
			if err != nil {
				return false, err
			}

			var composeTag string
			switch img.(type) {
			case reference.NamedTagged:
				composeTag = img.(reference.NamedTagged).Tag()
			default:
				logrus.Debugf("unable to parse %s, skipping", img)
				continue
			}

			composeImage := formatter.StripTagMeta(reference.Path(img))

			logrus.Debugf("parsed %s from %s", composeTag, service.Image)

			if image == composeImage {
				bytes, err := ioutil.ReadFile(composeFile)
				if err != nil {
					return false, err
				}

				old := fmt.Sprintf("%s:%s", composeImage, composeTag)
				new := fmt.Sprintf("%s:%s", composeImage, tag)
				replacedBytes := strings.Replace(string(bytes), old, new, -1)

				logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename)

				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
					return false, err
				}
			}
		}
	}

	return false, nil
}

// UpdateLabel updates a label in-place on file system local compose files.
func UpdateLabel(pattern, serviceName, label, recipeName string) error {
	composeFiles, err := filepath.Glob(pattern)
	if err != nil {
		return err
	}

	logrus.Debugf("considering %s config(s) for label update", strings.Join(composeFiles, ", "))

	for _, composeFile := range composeFiles {
		opts := stack.Deploy{Composefiles: []string{composeFile}}

		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
		sampleEnv, err := config.ReadEnv(envSamplePath)
		if err != nil {
			return err
		}

		compose, err := loader.LoadComposefile(opts, sampleEnv)
		if err != nil {
			return err
		}

		serviceExists := false
		var service composetypes.ServiceConfig
		for _, s := range compose.Services {
			if s.Name == serviceName {
				service = s
				serviceExists = true
			}
		}

		if !serviceExists {
			continue
		}

		discovered := false
		for oldLabel, value := range service.Deploy.Labels {
			if strings.HasPrefix(oldLabel, "coop-cloud") {
				discovered = true

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

				old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value)
				replacedBytes := strings.Replace(string(bytes), old, label, -1)

				if old == label {
					logrus.Warnf("%s is already set, nothing to do?", label)
					return nil
				}

				logrus.Debugf("updating %s to %s in %s", old, label, compose.Filename)

				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
					return err
				}

				logrus.Infof("synced label %s to service %s", label, serviceName)
			}
		}

		if !discovered {
			logrus.Warn("no existing label found, automagic insertion not supported yet")
			logrus.Fatalf("add '- \"%s\"' manually to the 'app' service in %s", label, composeFile)
		}
	}

	return nil
}