package export_svc

import (
	"strconv"
	"testing"
	"time"

	"github.com/smartystreets/goconvey/convey"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
)

// tsoFromTime builds a TiDB TSO from a wall-clock time: physical milliseconds in
// the high bits, an 18-bit logical counter in the low bits.
func tsoFromTime(t time.Time, logical uint64) uint64 {
	return uint64(t.UnixMilli())<<18 | (logical & ((1 << 18) - 1))
}

func TestParseSnapshotTSO(t *testing.T) {
	convey.Convey("parseSnapshotTSO", t, func() {
		convey.Convey("a valid TSO maps to its physical millisecond time", func() {
			base := time.Now().UTC().Add(-time.Hour).Truncate(time.Millisecond)
			tso := tsoFromTime(base, 0)

			got, err := parseSnapshotTSO(strconv.FormatUint(tso, 10))

			convey.So(err, convey.ShouldBeNil)
			convey.So(got, convey.ShouldNotBeNil)
			convey.So(got.UnixMilli(), convey.ShouldEqual, base.UnixMilli())
		})

		convey.Convey("the logical bits are dropped but the effective TSO round-trips", func() {
			base := time.Now().UTC().Add(-time.Hour).Truncate(time.Millisecond)
			// a non-zero logical counter must not change the physical time
			tso := tsoFromTime(base, 12345)

			got, err := parseSnapshotTSO(strconv.FormatUint(tso, 10))

			convey.So(err, convey.ShouldBeNil)
			// The export pipeline reconstructs the effective TSO as
			// SnapshotTime.UnixMilli()<<18, which equals the input TSO with its
			// logical bits zeroed. This is the exact value GetExport returns.
			effective := got.UnixMilli() << 18
			convey.So(effective, convey.ShouldEqual, base.UnixMilli()<<18)
			convey.So(effective, convey.ShouldEqual, int64(tso>>18)<<18)
		})

		convey.Convey("a non-numeric TSO is rejected", func() {
			_, err := parseSnapshotTSO("not-a-number")
			convey.So(err, convey.ShouldNotBeNil)
			convey.So(err.RawErrCode(), convey.ShouldEqual, errorcode.ErrCodeInvalidSnapshotTime)
		})

		convey.Convey("a zero TSO is rejected", func() {
			_, err := parseSnapshotTSO("0")
			convey.So(err, convey.ShouldNotBeNil)
			convey.So(err.RawErrCode(), convey.ShouldEqual, errorcode.ErrCodeInvalidSnapshotTime)
		})

		convey.Convey("a TSO in the future is rejected", func() {
			future := time.Now().UTC().Add(time.Hour).Truncate(time.Millisecond)
			tso := tsoFromTime(future, 0)

			_, err := parseSnapshotTSO(strconv.FormatUint(tso, 10))

			convey.So(err, convey.ShouldNotBeNil)
			convey.So(err.RawErrCode(), convey.ShouldEqual, errorcode.ErrCodeInvalidSnapshotTime)
		})
	})
}
