package template

import (
	"github.com/moby/swarmkit/v2/agent/exec"
	"github.com/moby/swarmkit/v2/api"
	"github.com/pkg/errors"
)

type templatedSecretGetter struct {
	dependencies exec.DependencyGetter
	t            *api.Task
	node         *api.NodeDescription
}

// NewTemplatedSecretGetter returns a SecretGetter that evaluates templates.
func NewTemplatedSecretGetter(dependencies exec.DependencyGetter, t *api.Task, node *api.NodeDescription) exec.SecretGetter {
	return templatedSecretGetter{dependencies: dependencies, t: t, node: node}
}

func (t templatedSecretGetter) Get(secretID string) (*api.Secret, error) {
	if t.dependencies == nil {
		return nil, errors.New("no secret provider available")
	}

	secrets := t.dependencies.Secrets()
	if secrets == nil {
		return nil, errors.New("no secret provider available")
	}

	secret, err := secrets.Get(secretID)
	if err != nil {
		return secret, err
	}

	newSpec, err := ExpandSecretSpec(secret, t.node, t.t, t.dependencies)
	if err != nil {
		return secret, errors.Wrapf(err, "failed to expand templated secret %s", secretID)
	}

	secretCopy := *secret
	secretCopy.Spec = *newSpec
	return &secretCopy, nil
}

// TemplatedConfigGetter is a ConfigGetter with an additional method to expose
// whether a config contains sensitive data.
type TemplatedConfigGetter interface {
	exec.ConfigGetter

	// GetAndFlagSecretData returns the interpolated config, and also
	// returns true if the config has been interpolated with data from a
	// secret. In this case, the config should be handled specially and
	// should not be written to disk.
	GetAndFlagSecretData(configID string) (*api.Config, bool, error)
}

type templatedConfigGetter struct {
	dependencies exec.DependencyGetter
	t            *api.Task
	node         *api.NodeDescription
}

// NewTemplatedConfigGetter returns a ConfigGetter that evaluates templates.
func NewTemplatedConfigGetter(dependencies exec.DependencyGetter, t *api.Task, node *api.NodeDescription) TemplatedConfigGetter {
	return templatedConfigGetter{dependencies: dependencies, t: t, node: node}
}

func (t templatedConfigGetter) Get(configID string) (*api.Config, error) {
	config, _, err := t.GetAndFlagSecretData(configID)
	return config, err
}

func (t templatedConfigGetter) GetAndFlagSecretData(configID string) (*api.Config, bool, error) {
	if t.dependencies == nil {
		return nil, false, errors.New("no config provider available")
	}

	configs := t.dependencies.Configs()
	if configs == nil {
		return nil, false, errors.New("no config provider available")
	}

	config, err := configs.Get(configID)
	if err != nil {
		return config, false, err
	}

	newSpec, sensitive, err := ExpandConfigSpec(config, t.node, t.t, t.dependencies)
	if err != nil {
		return config, false, errors.Wrapf(err, "failed to expand templated config %s", configID)
	}

	configCopy := *config
	configCopy.Spec = *newSpec
	return &configCopy, sensitive, nil
}

type templatedDependencyGetter struct {
	secrets exec.SecretGetter
	configs TemplatedConfigGetter
	volumes exec.VolumeGetter
}

// NewTemplatedDependencyGetter returns a DependencyGetter that evaluates templates.
func NewTemplatedDependencyGetter(dependencies exec.DependencyGetter, t *api.Task, node *api.NodeDescription) exec.DependencyGetter {
	return templatedDependencyGetter{
		secrets: NewTemplatedSecretGetter(dependencies, t, node),
		configs: NewTemplatedConfigGetter(dependencies, t, node),
		volumes: dependencies.Volumes(),
	}
}

func (t templatedDependencyGetter) Secrets() exec.SecretGetter {
	return t.secrets
}

func (t templatedDependencyGetter) Configs() exec.ConfigGetter {
	return t.configs
}

func (t templatedDependencyGetter) Volumes() exec.VolumeGetter {
	// volumes are not templated, but we include that call (and pass it
	// straight through to the underlying getter) in order to fulfill the
	// DependencyGetter interface.
	return t.volumes
}
