package envfile_test

import (
	"fmt"
	"os"
	"path"
	"reflect"
	"slices"
	"strings"
	"testing"

	appPkg "coopcloud.tech/abra/pkg/app"
	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/envfile"
	"coopcloud.tech/abra/pkg/recipe"
)

var (
	TestFolder    = os.ExpandEnv("$PWD/../../tests/resources/test_folder")
	ValidAbraConf = os.ExpandEnv("$PWD/../../tests/resources/valid_abra_config")
)

// make sure these are in alphabetical order
var (
	TFolders = []string{"folder1", "folder2"}
	TFiles   = []string{"bar.env", "foo.env"}
)

var (
	AppName    = "ecloud"
	ServerName = "evil.corp"
)

var ExpectedAppEnv = envfile.AppEnv{
	"DOMAIN": "ecloud.evil.corp",
	"RECIPE": "ecloud",
}

var ExpectedApp = appPkg.App{
	Name:   AppName,
	Recipe: ExpectedAppEnv["RECIPE"],
	Domain: ExpectedAppEnv["DOMAIN"],
	Env:    ExpectedAppEnv,
	Path:   ExpectedAppFile.Path,
	Server: ExpectedAppFile.Server,
}

var ExpectedAppFile = appPkg.AppFile{
	Path:   path.Join(ValidAbraConf, "servers", ServerName, AppName+".env"),
	Server: ServerName,
}

var ExpectedAppFiles = map[string]appPkg.AppFile{
	AppName: ExpectedAppFile,
}

func TestGetAllFoldersInDirectory(t *testing.T) {
	folders, err := config.GetAllFoldersInDirectory(TestFolder)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(folders, TFolders) {
		t.Fatalf("did not get expected folders. Expected: (%s), Got: (%s)", strings.Join(TFolders, ","), strings.Join(folders, ","))
	}
}

func TestGetAllFilesInDirectory(t *testing.T) {
	files, err := config.GetAllFilesInDirectory(TestFolder)
	if err != nil {
		t.Fatal(err)
	}
	var fileNames []string
	for _, file := range files {
		fileNames = append(fileNames, file.Name())
	}
	if !reflect.DeepEqual(fileNames, TFiles) {
		t.Fatalf("did not get expected files. Expected: (%s), Got: (%s)", strings.Join(TFiles, ","), strings.Join(fileNames, ","))
	}
}

func TestReadEnv(t *testing.T) {
	env, err := envfile.ReadEnv(ExpectedAppFile.Path)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(env, ExpectedAppEnv) {
		t.Fatalf(
			"did not get expected application settings. Expected: DOMAIN=%s RECIPE=%s; Got: DOMAIN=%s RECIPE=%s",
			ExpectedAppEnv["DOMAIN"],
			ExpectedAppEnv["RECIPE"],
			env["DOMAIN"],
			env["RECIPE"],
		)
	}
}

func TestReadAbraShEnvVars(t *testing.T) {
	offline := true
	r, err := recipe.Get("abra-test-recipe", offline)
	if err != nil {
		t.Fatal(err)
	}

	abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, r.Name, "abra.sh")
	abraShEnv, err := envfile.ReadAbraShEnvVars(abraShPath)
	if err != nil {
		t.Fatal(err)
	}

	if len(abraShEnv) == 0 {
		t.Error("at least one env var should be exported")
	}

	if _, ok := abraShEnv["INNER_FOO"]; ok {
		t.Error("INNER_FOO should not be exported")
	}

	if _, ok := abraShEnv["INNER_BAZ"]; ok {
		t.Error("INNER_BAZ should not be exported")
	}

	if _, ok := abraShEnv["OUTER_FOO"]; !ok {
		t.Error("OUTER_FOO should be exported")
	}
}

func TestReadAbraShCmdNames(t *testing.T) {
	offline := true
	r, err := recipe.Get("abra-test-recipe", offline)
	if err != nil {
		t.Fatal(err)
	}

	abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, r.Name, "abra.sh")
	cmdNames, err := appPkg.ReadAbraShCmdNames(abraShPath)
	if err != nil {
		t.Fatal(err)
	}

	if len(cmdNames) == 0 {
		t.Error("at least one command name should be found")
	}

	expectedCmdNames := []string{"test_cmd", "test_cmd_args"}
	for _, cmdName := range expectedCmdNames {
		if !slices.Contains(cmdNames, cmdName) {
			t.Fatalf("%s should have been found in %s", cmdName, abraShPath)
		}
	}
}

func TestCheckEnv(t *testing.T) {
	offline := true
	r, err := recipe.Get("abra-test-recipe", offline)
	if err != nil {
		t.Fatal(err)
	}

	envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
	envSample, err := envfile.ReadEnv(envSamplePath)
	if err != nil {
		t.Fatal(err)
	}

	app := appPkg.App{
		Name:   "test-app",
		Recipe: r.Name,
		Domain: "example.com",
		Env:    envSample,
		Path:   "example.com.env",
		Server: "example.com",
	}

	envVars, err := appPkg.CheckEnv(app)
	if err != nil {
		t.Fatal(err)
	}

	for _, envVar := range envVars {
		if !envVar.Present {
			t.Fatalf("%s should be present", envVar.Name)
		}
	}
}

func TestCheckEnvError(t *testing.T) {
	offline := true
	r, err := recipe.Get("abra-test-recipe", offline)
	if err != nil {
		t.Fatal(err)
	}

	envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
	envSample, err := envfile.ReadEnv(envSamplePath)
	if err != nil {
		t.Fatal(err)
	}

	delete(envSample, "DOMAIN")

	app := appPkg.App{
		Name:   "test-app",
		Recipe: r.Name,
		Domain: "example.com",
		Env:    envSample,
		Path:   "example.com.env",
		Server: "example.com",
	}

	envVars, err := appPkg.CheckEnv(app)
	if err != nil {
		t.Fatal(err)
	}

	for _, envVar := range envVars {
		if envVar.Name == "DOMAIN" && envVar.Present {
			t.Fatalf("%s should not be present", envVar.Name)
		}
	}
}

func TestEnvVarCommentsRemoved(t *testing.T) {
	offline := true
	r, err := recipe.Get("abra-test-recipe", offline)
	if err != nil {
		t.Fatal(err)
	}

	envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
	envSample, err := envfile.ReadEnv(envSamplePath)
	if err != nil {
		t.Fatal(err)
	}

	envVar, exists := envSample["WITH_COMMENT"]
	if !exists {
		t.Fatal("WITH_COMMENT env var should be present in .env.sample")
	}

	if strings.Contains(envVar, "should be removed") {
		t.Fatalf("comment from '%s' should be removed", envVar)
	}

	envVar, exists = envSample["SECRET_TEST_PASS_TWO_VERSION"]
	if !exists {
		t.Fatal("WITH_COMMENT env var should be present in .env.sample")
	}

	if strings.Contains(envVar, "length") {
		t.Fatal("comment from env var SECRET_TEST_PASS_TWO_VERSION should have been removed")
	}
}

func TestEnvVarModifiersIncluded(t *testing.T) {
	offline := true
	r, err := recipe.Get("abra-test-recipe", offline)
	if err != nil {
		t.Fatal(err)
	}

	envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
	envSample, modifiers, err := envfile.ReadEnvWithModifiers(envSamplePath)
	if err != nil {
		t.Fatal(err)
	}

	if !strings.Contains(envSample["SECRET_TEST_PASS_TWO_VERSION"], "v1") {
		t.Errorf("value should be 'v1', got: '%s'", envSample["SECRET_TEST_PASS_TWO_VERSION"])
	}
	if modifiers == nil || modifiers["SECRET_TEST_PASS_TWO_VERSION"] == nil {
		t.Errorf("no modifiers included")
	} else {
		if modifiers["SECRET_TEST_PASS_TWO_VERSION"]["length"] != "10" {
			t.Errorf("length modifier should be '10', got: '%s'", modifiers["SECRET_TEST_PASS_TWO_VERSION"]["length"])
		}
	}
}