package config_test

import (
	"net/url"
	"os"
	"runtime"
	"strings"
	"testing"

	"git.netflux.io/rob/clipper/config"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type env map[string]string

// clearenv clears prefixed keys from the environment. Currently it does not
// clear AWS_* environment variables.
func clearenv() {
	for _, kv := range os.Environ() {
		split := strings.SplitN(kv, "=", 2)
		k := split[0]
		if !strings.HasPrefix(k, config.Prefix) {
			continue
		}
		os.Unsetenv(k)
	}
}

// setupenv sets up a valid environment, including AWS_* configuration
// variables.
func setupenv() {
	e := env{
		"CLIPPER_ENV":                  "development",
		"CLIPPER_DATABASE_URL":         "postgresql://localhost:5432/db",
		"CLIPPER_FILE_STORE_HTTP_ROOT": "/data",
		"CLIPPER_S3_BUCKET":            "bucket",
		"AWS_ACCESS_KEY_ID":            "key",
		"AWS_SECRET_ACCESS_KEY":        "secret",
		"AWS_REGION":                   "eu-west-1",
	}
	for k, v := range e {
		os.Setenv(k, v)
	}
}

func mustParseURL(t *testing.T, u string) *url.URL {
	pu, err := url.Parse(u)
	require.NoError(t, err)
	return pu
}

func TestNewFromEnv(t *testing.T) {
	t.Run("ENV", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Setenv("CLIPPER_ENV", "foo")
		c, err := config.NewFromEnv()
		assert.EqualError(t, err, "invalid CLIPPER_ENV value: foo")

		os.Setenv("CLIPPER_ENV", "")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.EnvDevelopment, c.Environment)

		os.Setenv("CLIPPER_ENV", "development")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.EnvDevelopment, c.Environment)

		os.Setenv("CLIPPER_ENV", "production")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.EnvProduction, c.Environment)
	})

	t.Run("BIND_ADDR", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Setenv("CLIPPER_BIND_ADDR", "")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.DefaultBindAddr, c.BindAddr)

		os.Setenv("CLIPPER_BIND_ADDR", "example.com:1234")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "example.com:1234", c.BindAddr)
	})

	t.Run("TLS_CERT_FILE and TLS_KEY_FILE", func(t *testing.T) {
		defer clearenv()
		setupenv()

		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "", c.TLSCertFile)
		assert.Equal(t, "", c.TLSKeyFile)

		const expErr = "both CLIPPER_TLS_CERT_FILE and CLIPPER_TLS_KEY_FILE must be set"
		os.Setenv("CLIPPER_TLS_CERT_FILE", "foo")
		os.Setenv("CLIPPER_TLS_KEY_FILE", "")
		c, err = config.NewFromEnv()
		assert.EqualError(t, err, expErr)

		os.Setenv("CLIPPER_TLS_CERT_FILE", "")
		os.Setenv("CLIPPER_TLS_KEY_FILE", "bar")
		c, err = config.NewFromEnv()
		assert.EqualError(t, err, expErr)

		os.Setenv("CLIPPER_TLS_CERT_FILE", "foo")
		os.Setenv("CLIPPER_TLS_KEY_FILE", "bar")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "foo", c.TLSCertFile)
		assert.Equal(t, "bar", c.TLSKeyFile)
	})

	t.Run("DATABASE_URL", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Unsetenv("CLIPPER_DATABASE_URL")
		_, err := config.NewFromEnv()
		assert.EqualError(t, err, "CLIPPER_DATABASE_URL not set")

		os.Setenv("CLIPPER_DATABASE_URL", "foo")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "foo", c.DatabaseURL)
	})

	t.Run("FILE_STORE", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Unsetenv("CLIPPER_FILE_STORE")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.FileSystemStore, c.FileStore)

		os.Setenv("CLIPPER_FILE_STORE", "foo")
		c, err = config.NewFromEnv()
		assert.EqualError(t, err, "invalid CLIPPER_FILE_STORE value: foo")

		os.Setenv("CLIPPER_FILE_STORE", "filesystem")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.FileSystemStore, c.FileStore)

		os.Setenv("CLIPPER_FILE_STORE", "s3")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, config.S3Store, c.FileStore)
	})

	t.Run("FILE_STORE_HTTP_ROOT", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Unsetenv("CLIPPER_FILE_STORE_HTTP_ROOT")
		_, err := config.NewFromEnv()
		require.EqualError(t, err, "FILE_STORE_HTTP_ROOT not set")

		os.Setenv("CLIPPER_FILE_STORE_HTTP_ROOT", "/foo")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "/foo", c.FileStoreHTTPRoot)
	})

	t.Run("FILE_STORE_HTTP_BASE_URL", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Setenv("CLIPPER_FILE_STORE_HTTP_BASE_URL", "%%")
		_, err := config.NewFromEnv()
		require.EqualError(t, err, "invalid CLIPPER_FILE_STORE_HTTP_BASE_URL: %%/")

		os.Unsetenv("CLIPPER_FILE_STORE_HTTP_BASE_URL")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, mustParseURL(t, "/"), c.FileStoreHTTPBaseURL)

		os.Setenv("CLIPPER_FILE_STORE_HTTP_BASE_URL", "/foo")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, mustParseURL(t, "/foo/"), c.FileStoreHTTPBaseURL)

		os.Setenv("CLIPPER_FILE_STORE_HTTP_BASE_URL", "/foo/")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, mustParseURL(t, "/foo/"), c.FileStoreHTTPBaseURL)

		os.Setenv("CLIPPER_FILE_STORE_HTTP_BASE_URL", "https://www.example.com/foo")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, mustParseURL(t, "https://www.example.com/foo/"), c.FileStoreHTTPBaseURL)
	})

	t.Run("ASSETS_HTTP_ROOT", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Unsetenv("CLIPPER_ASSETS_HTTP_ROOT")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "", c.AssetsHTTPRoot)

		os.Setenv("CLIPPER_ASSETS_HTTP_ROOT", "/bar")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "/bar", c.AssetsHTTPRoot)
	})

	t.Run("FFMPEG_WORKER_POOL_SIZE", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Setenv("CLIPPER_FFMPEG_WORKER_POOL_SIZE", "nope")
		c, err := config.NewFromEnv()
		assert.EqualError(t, err, "invalid CLIPPER_FFMPEG_WORKER_POOL_SIZE value: nope")

		os.Unsetenv("CLIPPER_FFMPEG_WORKER_POOL_SIZE")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, runtime.NumCPU(), c.FFmpegWorkerPoolSize)

		os.Setenv("CLIPPER_FFMPEG_WORKER_POOL_SIZE", "10")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, 10, c.FFmpegWorkerPoolSize)
	})

	t.Run("AWS configuration", func(t *testing.T) {
		defer clearenv()
		setupenv()
		os.Setenv("CLIPPER_FILE_STORE", "s3")

		os.Unsetenv("AWS_ACCESS_KEY_ID")
		_, err := config.NewFromEnv()
		assert.EqualError(t, err, "AWS_ACCESS_KEY_ID not set")

		os.Setenv("AWS_ACCESS_KEY_ID", "key")
		os.Unsetenv("AWS_SECRET_ACCESS_KEY")
		_, err = config.NewFromEnv()
		assert.EqualError(t, err, "AWS_SECRET_ACCESS_KEY not set")

		os.Setenv("AWS_ACCESS_KEY_ID", "key")
		os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
		os.Unsetenv("AWS_REGION")
		_, err = config.NewFromEnv()
		assert.EqualError(t, err, "AWS_REGION not set")

		os.Setenv("AWS_ACCESS_KEY_ID", "key")
		os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
		os.Setenv("AWS_REGION", "eu-west-1")
		os.Unsetenv("CLIPPER_S3_BUCKET")
		_, err = config.NewFromEnv()
		assert.EqualError(t, err, "S3_BUCKET not set")

		os.Setenv("AWS_ACCESS_KEY_ID", "key")
		os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
		os.Setenv("AWS_REGION", "eu-west-1")
		os.Setenv("CLIPPER_S3_BUCKET", "bucket")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, "key", c.AWSAccessKeyID)
		assert.Equal(t, "secret", c.AWSSecretAccessKey)
		assert.Equal(t, "eu-west-1", c.AWSRegion)
		assert.Equal(t, "bucket", c.S3Bucket)
	})

	t.Run("CORS_ALLOWED_ORIGINS", func(t *testing.T) {
		defer clearenv()
		setupenv()

		os.Setenv("CLIPPER_CORS_ALLOWED_ORIGINS", "")
		c, err := config.NewFromEnv()
		require.NoError(t, err)
		assert.Nil(t, c.CORSAllowedOrigins)

		os.Setenv("CLIPPER_CORS_ALLOWED_ORIGINS", "*")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, []string{"*"}, c.CORSAllowedOrigins)

		os.Setenv("CLIPPER_CORS_ALLOWED_ORIGINS", "https://www1.example.com,https://www2.example.com")
		c, err = config.NewFromEnv()
		require.NoError(t, err)
		assert.Equal(t, []string{"https://www1.example.com", "https://www2.example.com"}, c.CORSAllowedOrigins)
	})
}
