package tester

import (
	"fmt"
	"os"
	"slices"
)

// EnvTest Environment variables manager for tests.
type EnvTest struct {
	keys   []string
	values map[string]string

	liveTestHook      func() bool
	liveTestExtraHook func() bool

	domain    string
	domainKey string
}

// NewEnvTest Creates an EnvTest.
func NewEnvTest(keys ...string) *EnvTest {
	values := make(map[string]string)
	for _, key := range keys {
		value := os.Getenv(key)
		if value != "" {
			values[key] = value
		}
	}

	return &EnvTest{
		keys:   keys,
		values: values,
	}
}

// WithDomain Defines the name of the environment variable used to define the domain related to the DNS request.
// If the domain is defined, it was considered mandatory to define a test as a "live" test.
func (e *EnvTest) WithDomain(key string) *EnvTest {
	e.domainKey = key
	e.domain = os.Getenv(key)
	return e
}

// WithLiveTestRequirements Defines the environment variables required to define a test as a "live" test.
// Replaces the default behavior (all keys are required).
func (e *EnvTest) WithLiveTestRequirements(keys ...string) *EnvTest {
	var countValuedVars int

	for _, key := range keys {
		if e.domainKey != key && !e.isManagedKey(key) {
			panic(fmt.Sprintf("Unauthorized action, the env var %s is not managed, or it's not the key of the domain.", key))
		}

		if e.domainKey == key {
			countValuedVars++
			continue
		}

		if _, ok := e.values[key]; ok {
			countValuedVars++
		}
	}

	live := countValuedVars != 0 && len(keys) == countValuedVars

	e.liveTestHook = func() bool {
		return live
	}

	return e
}

// WithLiveTestExtra Allows to define an additional condition to flag a test as "live" test.
// This does not replace the default behavior.
func (e *EnvTest) WithLiveTestExtra(extra func() bool) *EnvTest {
	e.liveTestExtraHook = extra
	return e
}

// GetDomain Gets the domain value associated with the DNS challenge (linked to WithDomain method).
func (e *EnvTest) GetDomain() string {
	return e.domain
}

// IsLiveTest Checks whether environment variables allow running a "live" test.
func (e *EnvTest) IsLiveTest() bool {
	liveTest := e.liveTestExtra()

	if e.liveTestHook != nil {
		return liveTest && e.liveTestHook()
	}

	liveTest = liveTest && len(e.values) == len(e.keys)

	if liveTest && e.domainKey != "" && e.domain == "" {
		return false
	}

	return liveTest
}

// RestoreEnv Restores the environment variables to the initial state.
func (e *EnvTest) RestoreEnv() {
	for key, value := range e.values {
		os.Setenv(key, value)
	}
}

// ClearEnv Deletes all environment variables related to the test.
func (e *EnvTest) ClearEnv() {
	for _, key := range e.keys {
		os.Unsetenv(key)
	}
}

// GetValue Gets the stored value of an environment variable.
func (e *EnvTest) GetValue(key string) string {
	return e.values[key]
}

func (e *EnvTest) liveTestExtra() bool {
	if e.liveTestExtraHook == nil {
		return true
	}

	return e.liveTestExtraHook()
}

// Apply Sets/Unsets environment variables.
// Not related to the main environment variables.
func (e *EnvTest) Apply(envVars map[string]string) {
	for key, value := range envVars {
		if !e.isManagedKey(key) {
			panic(fmt.Sprintf("Unauthorized action, the env var %s is not managed.", key))
		}

		if value == "" {
			os.Unsetenv(key)
		} else {
			os.Setenv(key, value)
		}
	}
}

func (e *EnvTest) isManagedKey(varName string) bool {
	return slices.Contains(e.keys, varName)
}