package ldvalue

import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/launchdarkly/go-jsonstream/v3/jreader"
	"github.com/launchdarkly/go-jsonstream/v3/jwriter"

	"github.com/stretchr/testify/assert"
)

func TestValueJSONMarshalUnmarshal(t *testing.T) {
	items := []struct {
		value Value
		json  string
	}{
		{Null(), nullAsJSON},
		{Bool(true), "true"},
		{Bool(false), "false"},
		{Int(1), "1"},
		{Float64(1), "1"},
		{Float64(2.5), "2.5"},
		{String("x"), `"x"`},
		{ArrayOf(), `[]`},
		{ArrayBuild().Add(Bool(true)).Add(String("x")).Build(), `[true,"x"]`},
		{ObjectBuild().Build(), `{}`},
		{ObjectBuild().Set("a", Bool(true)).Build(), `{"a":true}`},
	}
	for _, item := range items {
		t.Run(fmt.Sprintf("type %s, json %v", item.value.Type(), item.json), func(t *testing.T) {
			j, err := json.Marshal(item.value)
			assert.NoError(t, err)
			assert.Equal(t, item.json, string(j))

			assert.Equal(t, item.json, item.value.JSONString())
			assert.Equal(t, json.RawMessage(item.json), item.value.AsRaw())

			var v Value
			err = json.Unmarshal([]byte(item.json), &v)
			assert.NoError(t, err)
			assert.Equal(t, item.value, v)

			assert.Equal(t, item.value, Parse([]byte(item.json)))

			r := jreader.NewReader([]byte(item.json))
			var v1 Value
			v1.ReadFromJSONReader(&r)
			assert.NoError(t, r.Error())
			assert.Equal(t, item.value, v1)

			w := jwriter.NewWriter()
			item.value.WriteToJSONWriter(&w)
			bytes := w.Bytes()
			assert.NoError(t, w.Error())
			assert.Equal(t, item.json, string(bytes))
		})
	}
}

func TestMarshalRaw(t *testing.T) {
	// This is separate from the MarshalUnmarshal test because you never get a Raw when you unmarshal.
	for _, params := range []struct {
		desc   string
		input  json.RawMessage
		output string
	}{
		{"valid JSON", json.RawMessage(`{"a":1}`), `{"a":1}`},
		{"zero-length", json.RawMessage{}, `null`},
		{"nil", json.RawMessage(nil), `null`},
	} {
		t.Run(params.desc, func(t *testing.T) {
			value := Raw(params.input)

			bytes, err := json.Marshal(value)
			assert.NoError(t, err)
			assert.Equal(t, params.output, string(bytes))

			w := jwriter.NewWriter()
			value.WriteToJSONWriter(&w)
			assert.NoError(t, w.Error())
			assert.Equal(t, params.output, string(w.Bytes()))
		})
	}
}

func TestValueUnmarshalErrorConditions(t *testing.T) {
	var v Value
	for _, data := range [][]byte{
		nil,
		{},
		[]byte("what"),
		[]byte("["),
		[]byte("[what"),
		[]byte("{"),
		[]byte("{what"),
		[]byte(`{"no":what`),
		[]byte(`1,`),
	} {
		assert.Error(t, json.Unmarshal(data, &v))
		assert.Equal(t, Null(), Parse(data))
	}
}

func TestValueArrayJSONMarshalUnmarshal(t *testing.T) {
	items := []struct {
		valueArray ValueArray
		json       string
	}{
		{ValueArray{}, nullAsJSON},
		{ValueArrayBuild().Build(), `[]`},
		{ValueArrayOf(String("a"), String("b")), `["a","b"]`},
	}
	for _, item := range items {
		t.Run(fmt.Sprintf("json %v", item.json), func(t *testing.T) {
			j, err := json.Marshal(item.valueArray)
			assert.NoError(t, err)
			assert.Equal(t, item.json, string(j))

			assert.Equal(t, item.json, item.valueArray.JSONString())

			var a ValueArray
			err = json.Unmarshal([]byte(item.json), &a)
			assert.NoError(t, err)
			assert.Equal(t, item.valueArray, a)

			r := jreader.NewReader([]byte(item.json))
			a = ValueArray{}
			a.ReadFromJSONReader(&r)
			assert.NoError(t, r.Error())
			assert.Equal(t, item.valueArray, a)

			w := jwriter.NewWriter()
			item.valueArray.WriteToJSONWriter(&w)
			assert.NoError(t, w.Error())
			assert.Equal(t, item.json, string(w.Bytes()))
		})
	}

	for _, badJSON := range []string{"true", "1", `"x"`, "{}"} {
		err := json.Unmarshal([]byte(badJSON), &ValueArray{})
		assert.Error(t, err)
		assert.IsType(t, &json.UnmarshalTypeError{}, err)
	}
}

func TestValueMapJSONMarshalUnmarshal(t *testing.T) {
	items := []struct {
		valueMap ValueMap
		json     string
	}{
		{ValueMap{}, nullAsJSON},
		{ValueMapBuild().Build(), `{}`},
		{ValueMapBuild().Set("a", Bool(true)).Build(), `{"a":true}`},
	}
	for _, item := range items {
		t.Run(fmt.Sprintf("json %v", item.json), func(t *testing.T) {
			j, err := json.Marshal(item.valueMap)
			assert.NoError(t, err)
			assert.Equal(t, item.json, string(j))

			assert.Equal(t, item.json, item.valueMap.JSONString())

			var m ValueMap
			err = json.Unmarshal([]byte(item.json), &m)
			assert.NoError(t, err)
			assert.Equal(t, item.valueMap, m)

			r := jreader.NewReader([]byte(item.json))
			m = ValueMap{}
			m.ReadFromJSONReader(&r)
			assert.NoError(t, r.Error())
			assert.Equal(t, item.valueMap, m)

			w := jwriter.NewWriter()
			item.valueMap.WriteToJSONWriter(&w)
			assert.NoError(t, w.Error())
			assert.Equal(t, item.json, string(w.Bytes()))
		})
	}

	for _, badJSON := range []string{"true", "1", `"x"`, "[]"} {
		err := json.Unmarshal([]byte(badJSON), &ValueMap{})
		assert.Error(t, err)
		assert.IsType(t, &json.UnmarshalTypeError{}, err)
	}
}
