package object

import (
	"encoding/json"
	"testing"

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

type sampleObject struct {
	ID     int               `json:"id"`
	Name   string            `json:"name"`
	Labels []string          `json:"labels,omitempty"`
	Meta   map[string]string `json:"meta,omitempty"`
	Child  *sampleChild      `json:"child,omitempty"`
}

type sampleChild struct {
	Value string `json:"value"`
}

func TestPointer(t *testing.T) {
	t.Parallel()

	value := Pointer("workspace")

	require.NotNil(t, value)
	require.Equal(t, "workspace", *value)
}

func TestDeref(t *testing.T) {
	t.Parallel()

	require.Equal(t, "workspace", Deref(Pointer("workspace"), "default"))
	require.Equal(t, "default", Deref(nil, "default"))
}

func TestDezero(t *testing.T) {
	t.Parallel()

	require.Equal(t, "default", OrDefault("", "default"))
	require.Equal(t, "workspace", OrDefault("workspace", "default"))
	require.Equal(t, 10, OrDefault(0, 10))
	require.Equal(t, 5, OrDefault(5, 10))
}

func TestDerefAny(t *testing.T) {
	t.Parallel()

	require.Equal(t, 10, DerefAny(10, 1))
	require.Equal(t, 1, DerefAny("10", 1))
}

func TestDeepCopy(t *testing.T) {
	t.Parallel()

	origin := &sampleObject{
		ID:     1,
		Name:   "workspace",
		Labels: []string{"incident", "ai"},
		Meta:   map[string]string{"owner": "team-a"},
		Child:  &sampleChild{Value: "child-a"},
	}

	copied, err := DeepCopy(origin)

	require.NoError(t, err)
	require.Equal(t, origin, copied)
	require.NotSame(t, origin, copied)
	require.NotSame(t, origin.Child, copied.Child)

	copied.Labels[0] = "changed"
	copied.Meta["owner"] = "team-b"
	copied.Child.Value = "child-b"

	require.Equal(t, []string{"incident", "ai"}, origin.Labels)
	require.Equal(t, "team-a", origin.Meta["owner"])
	require.Equal(t, "child-a", origin.Child.Value)
}

func TestDeepCopyReturnsErrorForNilPointer(t *testing.T) {
	t.Parallel()

	copied, err := DeepCopy[sampleObject](nil)

	require.Nil(t, copied)
	require.ErrorContains(t, err, "nil pointer")
}

func TestStructToMap(t *testing.T) {
	t.Parallel()

	result, err := StructToMap(sampleObject{
		ID:   1,
		Name: "workspace",
	})

	require.NoError(t, err)
	require.Equal(t, map[string]any{
		"id":   float64(1),
		"name": "workspace",
	}, result)
}

func TestStructToMapReturnsMarshalError(t *testing.T) {
	t.Parallel()

	result, err := StructToMap(func() {})

	require.Nil(t, result)
	require.Error(t, err)
}

func TestMapToStruct(t *testing.T) {
	t.Parallel()

	var result sampleObject
	err := MapToStruct(map[string]any{
		"id":   float64(1),
		"name": "workspace",
	}, &result)

	require.NoError(t, err)
	require.Equal(t, sampleObject{ID: 1, Name: "workspace"}, result)
}

func TestMapToStructReturnsMarshalError(t *testing.T) {
	t.Parallel()

	var result sampleObject
	err := MapToStruct(map[string]any{"value": func() {}}, &result)

	require.Error(t, err)
}

func TestMapToStructReturnsUnmarshalError(t *testing.T) {
	t.Parallel()

	var result sampleObject
	err := MapToStruct(map[string]any{"id": "invalid"}, &result)

	require.Error(t, err)
}

func TestMarshalWithoutError(t *testing.T) {
	t.Parallel()

	data := MarshalWithoutError(sampleObject{ID: 1, Name: "workspace"})

	require.JSONEq(t, `{"id":1,"name":"workspace"}`, string(data))
}

func TestMarshalWithoutErrorReturnsErrorBytes(t *testing.T) {
	t.Parallel()

	data := MarshalWithoutError(func() {})

	var js json.RawMessage
	require.Error(t, json.Unmarshal(data, &js))
	require.Contains(t, string(data), "json: unsupported type: func()")
}
