package partners

import (
	"encoding/json"
	"testing"
)

// A client must be able to set a default address, point it somewhere else, and
// remove it entirely. The last of those is the one *int64 cannot express:
// encoding/json maps both a missing key and an explicit null to a nil pointer,
// so with a plain pointer there is no payload that clears the column.
//
// `**int64` was the obvious fix and does not work — json.Unmarshal sets the
// outer pointer to nil for a JSON null instead of allocating it, so the extra
// level collapses to the same nil. This is NullInt's whole reason to exist.
func TestNullIntSeparatesAbsentFromNull(t *testing.T) {
	type body struct {
		Billing NullInt `json:"billing_address_id"`
	}
	cases := []struct {
		raw           string
		wantPresent   bool
		wantValid     bool
		wantValue     int64
		wantExplained string
	}{
		{`{}`, false, false, 0, "missing key = leave the column alone"},
		{`{"billing_address_id":null}`, true, false, 0, "explicit null = CLEAR the column"},
		{`{"billing_address_id":42}`, true, true, 42, "a value = set it"},
	}
	for _, c := range cases {
		var v body
		if err := json.Unmarshal([]byte(c.raw), &v); err != nil {
			t.Errorf("%s: %v", c.raw, err)
			continue
		}
		got := v.Billing
		if got.Present != c.wantPresent || got.Valid != c.wantValid || got.Value != c.wantValue {
			t.Errorf("%-34s (%s) = %+v, want Present=%v Valid=%v Value=%d",
				c.raw, c.wantExplained, got, c.wantPresent, c.wantValid, c.wantValue)
		}
	}
}

// A pointer type cannot do this, and that is the finding worth pinning: if
// *int64 ever looks like the tidier answer, this test is the reason it is not.
func TestPlainInt64PointerCannotDistinguishNullFromAbsent(t *testing.T) {
	type plain struct {
		Billing *int64 `json:"billing_address_id"`
	}
	absent, withNull := plain{}, plain{}
	if err := json.Unmarshal([]byte(`{}`), &absent); err != nil {
		t.Fatal(err)
	}
	if err := json.Unmarshal([]byte(`{"billing_address_id":null}`), &withNull); err != nil {
		t.Fatal(err)
	}
	if absent.Billing != withNull.Billing {
		t.Skip("this Go version distinguishes them; NullInt is then redundant but still correct")
	}
	t.Log("confirmed: *int64 reports missing key and explicit null identically " +
		"as nil, so neither can mean \"clear\" while absent means \"leave alone\"")
}

// A non-numeric value must be a decode error, not a silent zero — otherwise
// {"billing_address_id":"abc"} would clear the pointer to 0-shaped garbage.
func TestNullIntRejectsNonNumeric(t *testing.T) {
	var v struct {
		Billing NullInt `json:"billing_address_id"`
	}
	if err := json.Unmarshal([]byte(`{"billing_address_id":"abc"}`), &v); err == nil {
		t.Error(`"abc" was accepted for an address id, want a decode error`)
	}
	if err := json.Unmarshal([]byte(`{"billing_address_id":1.5}`), &v); err == nil {
		t.Error("1.5 was accepted for an address id, want a decode error — ids are not fractional")
	}
}
