package config

import (
	"strings"
	"sync"
	"testing"
	"time"
)

func env(values map[string]string) Lookup {
	return func(k string) (string, bool) { v, ok := values[k]; return v, ok }
}
func TestLayers(t *testing.T) {
	cases := []struct {
		name, file  string
		values      map[string]string
		args        []string
		want        int
		bad         bool
		wantTimeout time.Duration
		wantDebug   bool
	}{
		{name: "default", want: 8080},
		{name: "file", file: `{"port":9000}`, want: 9000},
		{name: "env", file: `{"port":9000}`, values: map[string]string{"APP_PORT": "9001"}, want: 9001},
		{name: "flag", file: `{"port":9000}`, values: map[string]string{"APP_PORT": "9001"}, args: []string{"-port=9002"}, want: 9002},
		{name: "empty_env", values: map[string]string{"APP_PORT": ""}, bad: true},
		{name: "unknown_key", file: `{"prot":9000}`, bad: true},
		{name: "trailing_json", file: `{} {}`, bad: true},
		{name: "bad_port", args: []string{"-port=0"}, bad: true},
		{name: "file_fields", file: `{"timeout":"3s","debug":true}`, want: 8080, wantTimeout: 3 * time.Second, wantDebug: true},
		{name: "env_fields", values: map[string]string{"APP_TIMEOUT": "250ms", "APP_DEBUG": "true"}, want: 8080, wantTimeout: 250 * time.Millisecond, wantDebug: true},
		{name: "flag_fields", values: map[string]string{"APP_TIMEOUT": "3s", "APP_DEBUG": "true"}, args: []string{"-timeout=1s", "-debug=false"}, want: 8080, wantTimeout: time.Second},
		{name: "invalid_timeout_even_with_flag", values: map[string]string{"APP_TIMEOUT": "garbage"}, args: []string{"-timeout=1s"}, bad: true},
		{name: "invalid_debug", values: map[string]string{"APP_DEBUG": "garbage"}, bad: true},
		{name: "zero_timeout", args: []string{"-timeout=0s"}, bad: true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var c Config
			var err error
			if tc.file == "" {
				c, err = Load(nil, env(tc.values), tc.args)
			} else {
				c, err = Load(strings.NewReader(tc.file), env(tc.values), tc.args)
			}
			if (err != nil) != tc.bad {
				t.Fatalf("error=%v", err)
			}
			if !tc.bad && c.Port != tc.want {
				t.Fatalf("port=%d want=%d", c.Port, tc.want)
			}
			if !tc.bad {
				wantTimeout := tc.wantTimeout
				if wantTimeout == 0 {
					wantTimeout = 2 * time.Second
				}
				if c.Timeout != wantTimeout || c.Debug != tc.wantDebug {
					t.Fatalf("timeout=%s debug=%v", c.Timeout, c.Debug)
				}
			}
			t.Logf("port=%d timeout=%s debug=%v error=%v", c.Port, c.Timeout, c.Debug, err)
		})
	}
}
func TestSnapshot(t *testing.T) {
	c, err := Load(nil, env(nil), nil)
	if err != nil {
		t.Fatal(err)
	}
	m := NewManager(c)
	if err := m.Reload(nil, env(map[string]string{"APP_PORT": "-1"}), nil); err == nil {
		t.Fatal("wanted failure")
	}
	if m.Current().Port != 8080 {
		t.Fatal("failed reload published")
	}
	var wg sync.WaitGroup
	for range 4 {
		wg.Go(func() {
			for range 100 {
				_ = m.Current()
			}
		})
	}
	if err := m.Reload(nil, env(map[string]string{"APP_PORT": "9000"}), nil); err != nil {
		t.Fatal(err)
	}
	wg.Wait()
	t.Logf("after failed reload=8080 after valid reload=%d", m.Current().Port)
}
