package config

import (
	"bytes"
	"encoding/json"
	"testing"
	"time"
)

func TestOptionalDuration(t *testing.T) {
	makeDurationPointer := func(d time.Duration) *time.Duration { return &d }

	t.Run("marshalling and unmarshalling", func(t *testing.T) {
		out, err := json.Marshal(OptionalDuration{value: makeDurationPointer(time.Second)})
		if err != nil {
			t.Fatal(err)
		}
		expected := "\"1s\""
		if string(out) != expected {
			t.Fatalf("expected %s, got %s", expected, string(out))
		}
		var d OptionalDuration

		if err := json.Unmarshal(out, &d); err != nil {
			t.Fatal(err)
		}
		if *d.value != time.Second {
			t.Fatal("expected a second")
		}
	})

	t.Run("default value", func(t *testing.T) {
		for _, jsonStr := range []string{"null", "\"null\"", "\"\"", "\"default\""} {
			var d OptionalDuration
			if !d.IsDefault() {
				t.Fatal("expected value to be the default initially")
			}
			if err := json.Unmarshal([]byte(jsonStr), &d); err != nil {
				t.Fatalf("%s failed to unmarshall with %s", jsonStr, err)
			}
			if dur := d.WithDefault(time.Hour); dur != time.Hour {
				t.Fatalf("expected default value to be used, got %s", dur)
			}
			if !d.IsDefault() {
				t.Fatal("expected value to be the default")
			}
		}
	})

	t.Run("omitempty with default value", func(t *testing.T) {
		type Foo struct {
			D *OptionalDuration `json:",omitempty"`
		}
		// marshall to JSON without empty field
		out, err := json.Marshal(new(Foo))
		if err != nil {
			t.Fatal(err)
		}
		if string(out) != "{}" {
			t.Fatalf("expected omitempty to omit the duration, got %s", out)
		}
		// unmarshall missing value and get the default
		var foo2 Foo
		if err := json.Unmarshal(out, &foo2); err != nil {
			t.Fatalf("%s failed to unmarshall with %s", string(out), err)
		}
		if dur := foo2.D.WithDefault(time.Hour); dur != time.Hour {
			t.Fatalf("expected default value to be used, got %s", dur)
		}
		if !foo2.D.IsDefault() {
			t.Fatal("expected value to be the default")
		}
	})

	t.Run("roundtrip including the default values", func(t *testing.T) {
		for jsonStr, goValue := range map[string]OptionalDuration{
			// there are various footguns user can hit, normalize them to the canonical default
			"null":        {}, // JSON null → default value
			"\"null\"":    {}, // JSON string "null" sent/set by "ipfs config" cli → default value
			"\"default\"": {}, // explicit "default" as string
			"\"\"":        {}, // user removed custom value, empty string should also parse as default
			"\"1s\"":      {value: makeDurationPointer(time.Second)},
			"\"42h1m3s\"": {value: makeDurationPointer(42*time.Hour + 1*time.Minute + 3*time.Second)},
		} {
			var d OptionalDuration
			err := json.Unmarshal([]byte(jsonStr), &d)
			if err != nil {
				t.Fatal(err)
			}

			if goValue.value == nil && d.value == nil {
			} else if goValue.value == nil && d.value != nil {
				t.Errorf("expected nil for %s, got %s", jsonStr, d)
			} else if *d.value != *goValue.value {
				t.Fatalf("expected %s for %s, got %s", goValue, jsonStr, d)
			}

			// Test Reverse
			out, err := json.Marshal(goValue)
			if err != nil {
				t.Fatal(err)
			}
			if goValue.value == nil {
				if !bytes.Equal(out, []byte("null")) {
					t.Fatalf("expected JSON null for %s, got %s", jsonStr, string(out))
				}
				continue
			}
			if string(out) != jsonStr {
				t.Fatalf("expected %s, got %s", jsonStr, string(out))
			}
		}
	})

	t.Run("invalid duration values", func(t *testing.T) {
		for _, invalid := range []string{
			"\"s\"", "\"1ę\"", "\"-1\"", "\"1H\"", "\"day\"",
		} {
			var d OptionalDuration
			err := json.Unmarshal([]byte(invalid), &d)
			if err == nil {
				t.Errorf("expected to fail to decode %s as an OptionalDuration, got %s instead", invalid, d)
			}
		}
	})
}

func TestOneStrings(t *testing.T) {
	out, err := json.Marshal(Strings{"one"})
	if err != nil {
		t.Fatal(err)

	}
	expected := "\"one\""
	if string(out) != expected {
		t.Fatalf("expected %s, got %s", expected, string(out))
	}
}

func TestNoStrings(t *testing.T) {
	out, err := json.Marshal(Strings{})
	if err != nil {
		t.Fatal(err)

	}
	expected := "null"
	if string(out) != expected {
		t.Fatalf("expected %s, got %s", expected, string(out))
	}
}

func TestManyStrings(t *testing.T) {
	out, err := json.Marshal(Strings{"one", "two"})
	if err != nil {
		t.Fatal(err)

	}
	expected := "[\"one\",\"two\"]"
	if string(out) != expected {
		t.Fatalf("expected %s, got %s", expected, string(out))
	}
}

func TestFunkyStrings(t *testing.T) {
	toParse := " [   \"one\",   \"two\" ]  "
	var s Strings
	if err := json.Unmarshal([]byte(toParse), &s); err != nil {
		t.Fatal(err)
	}
	if len(s) != 2 || s[0] != "one" && s[1] != "two" {
		t.Fatalf("unexpected result: %v", s)
	}
}

func TestFlag(t *testing.T) {
	// make sure we have the right zero value.
	var defaultFlag Flag
	if defaultFlag != Default {
		t.Errorf("expected default flag to be %q, got %q", Default, defaultFlag)
	}

	if defaultFlag.WithDefault(true) != true {
		t.Error("expected default & true to be true")
	}

	if defaultFlag.WithDefault(false) != false {
		t.Error("expected default & false to be false")
	}

	if True.WithDefault(false) != true {
		t.Error("default should only apply to default")
	}

	if False.WithDefault(true) != false {
		t.Error("default should only apply to default")
	}

	if True.WithDefault(true) != true {
		t.Error("true & true is true")
	}

	if False.WithDefault(true) != false {
		t.Error("false & false is false")
	}

	for jsonStr, goValue := range map[string]Flag{
		"null":  Default,
		"true":  True,
		"false": False,
	} {
		var d Flag
		err := json.Unmarshal([]byte(jsonStr), &d)
		if err != nil {
			t.Fatal(err)
		}
		if d != goValue {
			t.Fatalf("expected %s, got %s", goValue, d)
		}

		// Reverse
		out, err := json.Marshal(goValue)
		if err != nil {
			t.Fatal(err)
		}
		if string(out) != jsonStr {
			t.Fatalf("expected %s, got %s", jsonStr, string(out))
		}
	}

	type Foo struct {
		F Flag `json:",omitempty"`
	}
	out, err := json.Marshal(new(Foo))
	if err != nil {
		t.Fatal(err)
	}
	expected := "{}"
	if string(out) != expected {
		t.Fatal("expected omitempty to omit the flag")
	}
}

func TestPriority(t *testing.T) {
	// make sure we have the right zero value.
	var defaultPriority Priority
	if defaultPriority != DefaultPriority {
		t.Errorf("expected default priority to be %q, got %q", DefaultPriority, defaultPriority)
	}

	if _, ok := defaultPriority.WithDefault(Disabled); ok {
		t.Error("should have been disabled")
	}

	if p, ok := defaultPriority.WithDefault(1); !ok || p != 1 {
		t.Errorf("priority should have been 1, got %d", p)
	}

	if p, ok := defaultPriority.WithDefault(DefaultPriority); !ok || p != 0 {
		t.Errorf("priority should have been 0, got %d", p)
	}

	for jsonStr, goValue := range map[string]Priority{
		"null":  DefaultPriority,
		"false": Disabled,
		"1":     1,
		"2":     2,
		"100":   100,
	} {
		var d Priority
		err := json.Unmarshal([]byte(jsonStr), &d)
		if err != nil {
			t.Fatal(err)
		}
		if d != goValue {
			t.Fatalf("expected %s, got %s", goValue, d)
		}

		// Reverse
		out, err := json.Marshal(goValue)
		if err != nil {
			t.Fatal(err)
		}
		if string(out) != jsonStr {
			t.Fatalf("expected %s, got %s", jsonStr, string(out))
		}
	}

	type Foo struct {
		P Priority `json:",omitempty"`
	}
	out, err := json.Marshal(new(Foo))
	if err != nil {
		t.Fatal(err)
	}
	expected := "{}"
	if string(out) != expected {
		t.Fatal("expected omitempty to omit the flag")
	}
	for _, invalid := range []string{
		"0", "-1", "-2", "1.1", "0.0",
	} {
		var p Priority
		err := json.Unmarshal([]byte(invalid), &p)
		if err == nil {
			t.Errorf("expected to fail to decode %s as a priority", invalid)
		}
	}
}

func TestOptionalInteger(t *testing.T) {
	makeInt64Pointer := func(v int64) *int64 {
		return &v
	}

	var defaultOptionalInt OptionalInteger
	if !defaultOptionalInt.IsDefault() {
		t.Fatal("should be the default")
	}
	if val := defaultOptionalInt.WithDefault(0); val != 0 {
		t.Errorf("optional integer should have been 0, got %d", val)
	}

	if val := defaultOptionalInt.WithDefault(1); val != 1 {
		t.Errorf("optional integer should have been 1, got %d", val)
	}

	if val := defaultOptionalInt.WithDefault(-1); val != -1 {
		t.Errorf("optional integer should have been -1, got %d", val)
	}

	var filledInt OptionalInteger
	filledInt = OptionalInteger{value: makeInt64Pointer(1)}
	if filledInt.IsDefault() {
		t.Fatal("should not be the default")
	}
	if val := filledInt.WithDefault(0); val != 1 {
		t.Errorf("optional integer should have been 1, got %d", val)
	}

	if val := filledInt.WithDefault(-1); val != 1 {
		t.Errorf("optional integer should have been 1, got %d", val)
	}

	filledInt = OptionalInteger{value: makeInt64Pointer(0)}
	if val := filledInt.WithDefault(1); val != 0 {
		t.Errorf("optional integer should have been 0, got %d", val)
	}

	for jsonStr, goValue := range map[string]OptionalInteger{
		"null": {},
		"0":    {value: makeInt64Pointer(0)},
		"1":    {value: makeInt64Pointer(1)},
		"-1":   {value: makeInt64Pointer(-1)},
	} {
		var d OptionalInteger
		err := json.Unmarshal([]byte(jsonStr), &d)
		if err != nil {
			t.Fatal(err)
		}

		if goValue.value == nil && d.value == nil {
		} else if goValue.value == nil && d.value != nil {
			t.Errorf("expected default, got %s", d)
		} else if *d.value != *goValue.value {
			t.Fatalf("expected %s, got %s", goValue, d)
		}

		// Reverse
		out, err := json.Marshal(goValue)
		if err != nil {
			t.Fatal(err)
		}
		if string(out) != jsonStr {
			t.Fatalf("expected %s, got %s", jsonStr, string(out))
		}
	}

	// marshal with omitempty
	type Foo struct {
		I *OptionalInteger `json:",omitempty"`
	}
	out, err := json.Marshal(new(Foo))
	if err != nil {
		t.Fatal(err)
	}
	expected := "{}"
	if string(out) != expected {
		t.Fatal("expected omitempty to omit the optional integer")
	}

	// unmarshal from omitempty output and get default value
	var foo2 Foo
	if err := json.Unmarshal(out, &foo2); err != nil {
		t.Fatalf("%s failed to unmarshall with %s", string(out), err)
	}
	if i := foo2.I.WithDefault(42); i != 42 {
		t.Fatalf("expected default value to be used, got %d", i)
	}
	if !foo2.I.IsDefault() {
		t.Fatal("expected value to be the default")
	}

	// test invalid values
	for _, invalid := range []string{
		"foo", "-1.1", "1.1", "0.0", "[]",
	} {
		var p OptionalInteger
		err := json.Unmarshal([]byte(invalid), &p)
		if err == nil {
			t.Errorf("expected to fail to decode %s as a priority", invalid)
		}
	}
}

func TestOptionalString(t *testing.T) {
	makeStringPointer := func(v string) *string {
		return &v
	}

	var defaultOptionalString OptionalString
	if !defaultOptionalString.IsDefault() {
		t.Fatal("should be the default")
	}
	if val := defaultOptionalString.WithDefault(""); val != "" {
		t.Errorf("optional string should have been empty, got %s", val)
	}
	if val := defaultOptionalString.String(); val != "default" {
		t.Fatalf("default optional string should be the 'default' string, got %s", val)
	}
	if val := defaultOptionalString.WithDefault("foo"); val != "foo" {
		t.Errorf("optional string should have been foo, got %s", val)
	}

	var filledStr OptionalString
	filledStr = OptionalString{value: makeStringPointer("foo")}
	if filledStr.IsDefault() {
		t.Fatal("should not be the default")
	}
	if val := filledStr.WithDefault("bar"); val != "foo" {
		t.Errorf("optional string should have been foo, got %s", val)
	}
	if val := filledStr.String(); val != "foo" {
		t.Fatalf("optional string should have been foo, got %s", val)
	}
	filledStr = OptionalString{value: makeStringPointer("")}
	if val := filledStr.WithDefault("foo"); val != "" {
		t.Errorf("optional string should have been 0, got %s", val)
	}

	for jsonStr, goValue := range map[string]OptionalString{
		"null":     {},
		"\"0\"":    {value: makeStringPointer("0")},
		"\"\"":     {value: makeStringPointer("")},
		`"1"`:      {value: makeStringPointer("1")},
		`"-1"`:     {value: makeStringPointer("-1")},
		`"qwerty"`: {value: makeStringPointer("qwerty")},
	} {
		var d OptionalString
		err := json.Unmarshal([]byte(jsonStr), &d)
		if err != nil {
			t.Fatal(err)
		}

		if goValue.value == nil && d.value == nil {
		} else if goValue.value == nil && d.value != nil {
			t.Errorf("expected default, got %s", d)
		} else if *d.value != *goValue.value {
			t.Fatalf("expected %s, got %s", goValue, d)
		}

		// Reverse
		out, err := json.Marshal(goValue)
		if err != nil {
			t.Fatal(err)
		}
		if string(out) != jsonStr {
			t.Fatalf("expected %s, got %s", jsonStr, string(out))
		}
	}

	// marshal with omitempty
	type Foo struct {
		S *OptionalString `json:",omitempty"`
	}
	out, err := json.Marshal(new(Foo))
	if err != nil {
		t.Fatal(err)
	}
	expected := "{}"
	if string(out) != expected {
		t.Fatal("expected omitempty to omit the optional integer")
	}
	// unmarshal from omitempty output and get default value
	var foo2 Foo
	if err := json.Unmarshal(out, &foo2); err != nil {
		t.Fatalf("%s failed to unmarshall with %s", string(out), err)
	}
	if s := foo2.S.WithDefault("foo"); s != "foo" {
		t.Fatalf("expected default value to be used, got %s", s)
	}
	if !foo2.S.IsDefault() {
		t.Fatal("expected value to be the default")
	}

	for _, invalid := range []string{
		"[]", "{}", "0", "a", "'b'",
	} {
		var p OptionalString
		err := json.Unmarshal([]byte(invalid), &p)
		if err == nil {
			t.Errorf("expected to fail to decode %s as an optional string", invalid)
		}
	}
}
