/*
 *
 * Copyright 2023 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package serviceconfig

import (
	"fmt"
	"math"
	"strings"
	"testing"
	"time"

	"google.golang.org/grpc/internal/grpcrand"
)

// Tests both marshalling and unmarshalling of Durations.
func TestDuration_MarshalUnmarshal(t *testing.T) {
	testCases := []struct {
		json         string
		td           time.Duration
		unmarshalErr error
		noMarshal    bool
	}{
		// Basic values.
		{json: `"1s"`, td: time.Second},
		{json: `"-100.700s"`, td: -100*time.Second - 700*time.Millisecond},
		{json: `".050s"`, td: 50 * time.Millisecond, noMarshal: true},
		{json: `"-.001s"`, td: -1 * time.Millisecond, noMarshal: true},
		{json: `"-0.200s"`, td: -200 * time.Millisecond},
		// Positive near / out of bounds.
		{json: `"9223372036s"`, td: 9223372036 * time.Second},
		{json: `"9223372037s"`, td: math.MaxInt64, noMarshal: true},
		{json: `"9223372036.854775807s"`, td: math.MaxInt64},
		{json: `"9223372036.854775808s"`, td: math.MaxInt64, noMarshal: true},
		{json: `"315576000000s"`, td: math.MaxInt64, noMarshal: true},
		{json: `"315576000001s"`, unmarshalErr: fmt.Errorf("out of range")},
		// Negative near / out of bounds.
		{json: `"-9223372036s"`, td: -9223372036 * time.Second},
		{json: `"-9223372037s"`, td: math.MinInt64, noMarshal: true},
		{json: `"-9223372036.854775808s"`, td: math.MinInt64},
		{json: `"-9223372036.854775809s"`, td: math.MinInt64, noMarshal: true},
		{json: `"-315576000000s"`, td: math.MinInt64, noMarshal: true},
		{json: `"-315576000001s"`, unmarshalErr: fmt.Errorf("out of range")},
		// Parse errors.
		{json: `123s`, unmarshalErr: fmt.Errorf("invalid character")},
		{json: `"5m"`, unmarshalErr: fmt.Errorf("malformed duration")},
		{json: `"5.3.2s"`, unmarshalErr: fmt.Errorf("malformed duration")},
		{json: `"x.3s"`, unmarshalErr: fmt.Errorf("malformed duration")},
		{json: `"3.xs"`, unmarshalErr: fmt.Errorf("malformed duration")},
		{json: `"3.1234567890s"`, unmarshalErr: fmt.Errorf("malformed duration")},
		{json: `".s"`, unmarshalErr: fmt.Errorf("malformed duration")},
		{json: `"s"`, unmarshalErr: fmt.Errorf("malformed duration")},
	}
	for _, tc := range testCases {
		// Seed `got` with a random value to ensure we properly reset it in all
		// non-error cases.
		got := Duration(grpcrand.Uint64())
		err := got.UnmarshalJSON([]byte(tc.json))
		if (err == nil && time.Duration(got) != tc.td) ||
			(err != nil) != (tc.unmarshalErr != nil) || !strings.Contains(fmt.Sprint(err), fmt.Sprint(tc.unmarshalErr)) {
			t.Errorf("UnmarshalJSON of %v = %v, %v; want %v, %v", tc.json, time.Duration(got), err, tc.td, tc.unmarshalErr)
		}

		if tc.unmarshalErr == nil && !tc.noMarshal {
			d := Duration(tc.td)
			got, err := d.MarshalJSON()
			if string(got) != tc.json || err != nil {
				t.Errorf("MarshalJSON of %v = %v, %v; want %v, nil", d, string(got), err, tc.json)
			}
		}
	}
}
