func MergeInterfaceMapToString(mp1, mp2 map[string]interface{}) (mp3 map[string]string) {
	mp3 = make(map[string]string)
	for k, v := range mp1 {
		if vs, ok := v.(string); ok {
			mp3[k] = vs
		}
	}
	for k, v := range mp2 {
		if vs, ok := v.(string); ok {
			mp3[k] = vs
		}
	}
	return mp3
}
merge two maps and return a map of string
it will ignore whatever is not a string type
package run
import (
	"context"
	"fmt"
	"time"
	"github.com/odpf/optimus/models"
	"github.com/pkg/errors"
)
const (
	TaskConfigPrefix = "TASK__"
	ProjectConfigPrefix = "GLOBAL__"
)
var (
	IgnoreTemplateRenderExtension = []string{".gtpl", ".j2", ".tmpl", ".tpl"}
)
type ContextManager struct {
	namespace models.NamespaceSpec
	jobRun    models.JobRun
	engine    models.TemplateEngine
}
func (fm *ContextManager) Generate(instanceSpec models.InstanceSpec) (
	envMap map[string]string, fileMap map[string]string, err error) {
	projectPrefixedConfig, projRawConfig := fm.projectEnvs()
	instanceEnvMap, instanceFileMap := fm.getInstanceData(instanceSpec)
	projectInstanceContext := MergeInterfaceMapToInterface(instanceEnvMap, projectPrefixedConfig)
	projectInstanceContext["proj"] = projRawConfig
	projectInstanceContext["inst"] = instanceEnvMap
	envMap, err = fm.generateEnvs(instanceSpec.Name, instanceSpec.Type, projectInstanceContext)
	if err != nil {
		return nil, nil, err
	}
	for k, v := range instanceEnvMap {
		if vs, ok := v.(string); ok {
			envMap[k] = vs
		}
	}
	compiledAssetResponse, err := fm.jobRun.Spec.Task.Unit.CLIMod.CompileAssets(context.Background(), models.CompileAssetsRequest{
		Window:           fm.jobRun.Spec.Task.Window,
		Config:           models.PluginConfigs{}.FromJobSpec(fm.jobRun.Spec.Task.Config),
		Assets:           models.PluginAssets{}.FromJobSpec(fm.jobRun.Spec.Assets),
		InstanceSchedule: fm.jobRun.ScheduledAt,
		InstanceData:     instanceSpec.Data,
	})
	if err != nil {
		return nil, nil, err
	}
	fileMap = MergeStringMap(instanceFileMap, compiledAssetResponse.Assets.ToJobSpec().ToMap())
	if fileMap, err = fm.engine.CompileFiles(fileMap, projectInstanceContext); err != nil {
		return
	}
	return envMap, fileMap, nil
}
func (fm *ContextManager) projectEnvs() (map[string]interface{}, map[string]interface{}) {
	projectPrefixedConfig := map[string]interface{}{}
	projRawConfig := map[string]interface{}{}
	for key, val := range fm.getProjectConfigMap() {
		projectPrefixedConfig[fmt.Sprintf("%s%s", ProjectConfigPrefix, key)] = val
		projRawConfig[key] = val
	}
	for key, val := range fm.getNamespaceConfigMap() {
		projectPrefixedConfig[fmt.Sprintf("%s%s", ProjectConfigPrefix, key)] = val
		projRawConfig[key] = val
	}
	return projectPrefixedConfig, projRawConfig
}
func (fm *ContextManager) generateEnvs(runName string, runType models.InstanceType,
	projectInstanceContext map[string]interface{}) (map[string]string, error) {
	transformationConfigs, hookConfigs, err := fm.getConfigMaps(fm.jobRun.Spec, runName, runType)
	if err != nil {
		return nil, err
	}
	if transformationConfigs, err = fm.compileTemplates(transformationConfigs, projectInstanceContext); err != nil {
		return nil, err
	}
	if runType == models.InstanceTypeTask {
		return MergeInterfaceMapToString(transformationConfigs, nil), nil
	}
	prefixedTransformationConfigs := map[string]interface{}{}
	for k, v := range transformationConfigs {
		prefixedTransformationConfigs[fmt.Sprintf("%s%s", TaskConfigPrefix, k)] = v
	}
	projectInstanceTransformationConfigs := MergeInterfaceMapToInterface(projectInstanceContext, prefixedTransformationConfigs)
	projectInstanceTransformationConfigs["task"] = transformationConfigs
	if hookConfigs, err = fm.compileTemplates(hookConfigs, projectInstanceTransformationConfigs); err != nil {
		return nil, err
	}
	return MergeInterfaceMapToString(prefixedTransformationConfigs, hookConfigs), nil
}
func (fm *ContextManager) compileTemplates(templateValueMap, templateContext map[string]interface{}) (map[string]interface{}, error) {
	for key, val := range templateValueMap {
		valString, ok := val.(string)
		if !ok {
			continue
		}
		compiledValue, err := fm.engine.CompileString(valString, templateContext)
		if err != nil {
			return nil, err
		}
		templateValueMap[key] = compiledValue
	}
	return templateValueMap, nil
}
func (fm *ContextManager) getProjectConfigMap() map[string]string {
	configMap := map[string]string{}
	for key, val := range fm.namespace.ProjectSpec.Config {
		configMap[key] = val
	}
	return configMap
}
func (fm *ContextManager) getNamespaceConfigMap() map[string]string {
	configMap := map[string]string{}
	for key, val := range fm.namespace.Config {
		configMap[key] = val
	}
	return configMap
}
func (fm *ContextManager) getInstanceData(instanceSpec models.InstanceSpec) (map[string]interface{}, map[string]string) {
	envMap := map[string]interface{}{}
	fileMap := map[string]string{}
	if instanceSpec.Data != nil {
		for _, jobRunData := range instanceSpec.Data {
			switch jobRunData.Type {
			case models.InstanceDataTypeFile:
				fileMap[jobRunData.Name] = jobRunData.Value
			case models.InstanceDataTypeEnv:
				envMap[jobRunData.Name] = jobRunData.Value
			}
		}
	}
	return envMap, fileMap
}
func (fm *ContextManager) getConfigMaps(jobSpec models.JobSpec, runName string,
	runType models.InstanceType) (map[string]interface{},
	map[string]interface{}, error) {
	transformationMap := map[string]interface{}{}
	for _, val := range jobSpec.Task.Config {
		transformationMap[val.Name] = val.Value
	}
	hookMap := map[string]interface{}{}
	if runType == models.InstanceTypeHook {
		hook, err := jobSpec.GetHookByName(runName)
		if err != nil {
			return nil, nil, errors.Wrapf(err, "requested hook not found %s", runName)
		}
		for _, val := range hook.Config {
			hookMap[val.Name] = val.Value
		}
	}
	return transformationMap, hookMap, nil
}
func NewContextManager(namespace models.NamespaceSpec, jobRun models.JobRun, engine models.TemplateEngine) *ContextManager {
	return &ContextManager{
		namespace: namespace,
		jobRun:    jobRun,
		engine:    engine,
	}
}
func MergeStringMap(mp1, mp2 map[string]string) (mp3 map[string]string) {
	mp3 = make(map[string]string)
	for k, v := range mp1 {
		mp3[k] = v
	}
	for k, v := range mp2 {
		mp3[k] = v
	}
	return mp3
}
func MergeInterfaceMapToInterface(mp1, mp2 map[string]interface{}) (mp3 map[string]interface{}) {
	mp3 = make(map[string]interface{})
	for k, v := range mp1 {
		mp3[k] = v
	}
	for k, v := range mp2 {
		mp3[k] = v
	}
	return mp3
}