package experiments

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/Masterminds/semver/v3"
	"github.com/joho/godotenv"
	"github.com/spf13/pflag"
	"gopkg.in/yaml.v3"
)

const envPrefix = "TASK_X_"

var defaultConfigFilenames = []string{
	".taskrc.yml",
	".taskrc.yaml",
}

type experimentConfigFile struct {
	Experiments map[string]int `yaml:"experiments"`
	Version     *semver.Version
}

var (
	GentleForce     Experiment
	RemoteTaskfiles Experiment
	AnyVariables    Experiment
	MapVariables    Experiment
	EnvPrecedence   Experiment
)

// An internal list of all the initialized experiments used for iterating.
var (
	xList            []Experiment
	experimentConfig experimentConfigFile
)

func init() {
	readDotEnv()
	experimentConfig = readConfig()
	GentleForce = New("GENTLE_FORCE", 1)
	RemoteTaskfiles = New("REMOTE_TASKFILES", 1)
	AnyVariables = New("ANY_VARIABLES")
	MapVariables = New("MAP_VARIABLES")
	EnvPrecedence = New("ENV_PRECEDENCE", 1)
}

// Validate checks if any experiments have been enabled while being inactive.
// If one is found, the function returns an error.
func Validate() error {
	for _, x := range List() {
		if err := x.Valid(); err != nil {
			return err
		}
	}
	return nil
}

func List() []Experiment {
	return xList
}

func getEnv(xName string) string {
	envName := fmt.Sprintf("%s%s", envPrefix, xName)
	return os.Getenv(envName)
}

func getFilePath(filename string) string {
	// Parse the CLI flags again to get the directory/taskfile being run
	// We use a flagset here so that we can parse a subset of flags without exiting on error.
	var dir, taskfile string
	fs := pflag.NewFlagSet("experiments", pflag.ContinueOnError)
	fs.StringVarP(&dir, "dir", "d", "", "Sets directory of execution.")
	fs.StringVarP(&taskfile, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
	fs.Usage = func() {}
	_ = fs.Parse(os.Args[1:])
	// If the directory is set, find a .env file in that directory.
	if dir != "" {
		return filepath.Join(dir, filename)
	}
	// If the taskfile is set, find a .env file in the directory containing the Taskfile.
	if taskfile != "" {
		return filepath.Join(filepath.Dir(taskfile), filename)
	}
	// Otherwise just use the current working directory.
	return filename
}

func readDotEnv() {
	env, _ := godotenv.Read(getFilePath(".env"))
	// If the env var is an experiment, set it.
	for key, value := range env {
		if strings.HasPrefix(key, envPrefix) {
			os.Setenv(key, value)
		}
	}
}

func readConfig() experimentConfigFile {
	var cfg experimentConfigFile

	var content []byte
	var err error
	for _, filename := range defaultConfigFilenames {
		path := getFilePath(filename)
		content, err = os.ReadFile(path)
		if err == nil {
			break
		}
	}

	if err != nil {
		return experimentConfigFile{}
	}

	if err := yaml.Unmarshal(content, &cfg); err != nil {
		return experimentConfigFile{}
	}

	return cfg
}