package idk

import (
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestDecimalFieldPilosafy(t *testing.T) {
	f := DecimalField{NameVal: "a", Scale: 3}
	if ret, err := f.PilosafyVal(float32(0.9)); err != nil || ret != int64(900) {
		t.Errorf("got: %v of %[1]T, err: %v", ret, err)
	}

}

func TestBoolFieldPilosafy(t *testing.T) {
	f := BoolField{NameVal: "boolcol"}
	validTrue := []interface{}{true, 1, "t", "true", " T ", " True"}
	validFalse := []interface{}{false, 0, "f", "false", " F ", " False"}
	invalid := []interface{}{"boat", " test "}

	for i, v := range validTrue {
		if ret, err := f.PilosafyVal(v); err != nil || ret != true {
			t.Errorf("test: %d, got: %v of %[1]T, err: %v", i, ret, err)
		}
	}
	for i, v := range validFalse {
		if ret, err := f.PilosafyVal(v); err != nil || ret != false {
			t.Errorf("test: %d, got: %v of %[1]T, err: %v", i, ret, err)
		}
	}
	for i, v := range invalid {
		if ret, err := f.PilosafyVal(v); err == nil {
			t.Errorf("test: %d, got: %v of %[1]T, err: %v", i, ret, err)
		}
	}
}

func TestPilosafyVal(t *testing.T) {
	tests := []struct {
		field   Field
		vals    []interface{}
		exps    []interface{}
		expErrs []string
	}{
		{
			field: StringArrayField{},
			vals: []interface{}{
				"a,b,c",
				"[a,b,c]",
				""},
			exps: []interface{}{
				[]string{"a", "b", "c"},
				[]string{"a", "b", "c"},
				[]string(nil)},
		},
		{
			field: IDArrayField{},
			vals: []interface{}{
				"1,2,3",
				"[1,2,3]",
				""},
			exps: []interface{}{
				[]uint64{1, 2, 3},
				[]uint64{1, 2, 3},
				[]uint64(nil)},
		},
		{
			field: IntField{},
			vals: []interface{}{
				1,
				"1",
				"",
				nil,
			},
			exps: []interface{}{
				int64(1),
				int64(1),
				nil,
				nil,
			},
		},
		{
			field: DecimalField{Scale: 2},
			vals: []interface{}{
				1,
				"1",
				"",
				nil,
			},
			exps: []interface{}{
				int64(100),
				int64(100),
				nil,
				nil,
			},
		},
		{
			field:   IDField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   BoolField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   StringField{},
			vals:    []interface{}{""},
			exps:    []interface{}{""},
			expErrs: []string(nil),
		},
		{
			field:   IntField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   DecimalField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   StringArrayField{},
			vals:    []interface{}{""},
			exps:    []interface{}{[]string(nil)},
			expErrs: []string(nil),
		},
		{
			field:   IDArrayField{},
			vals:    []interface{}{""},
			exps:    []interface{}{[]uint64(nil)},
			expErrs: []string(nil),
		},
		{
			field:   DateIntField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   RecordTimeField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   TimestampField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
		{
			field:   SignedIntBoolKeyField{},
			vals:    []interface{}{"", int64(-2), int(2), uint64(3)},
			exps:    []interface{}{nil, int64(-2), int64(2), int64(3)},
			expErrs: []string(nil),
		},
		{
			field:   IgnoreField{},
			vals:    []interface{}{""},
			exps:    []interface{}{nil},
			expErrs: []string(nil),
		},
	}

	for i, test := range tests {
		if len(test.exps) > 0 && len(test.vals) != len(test.exps) {
			panic(fmt.Sprintf("invalid test at %d", i))
		}
		if len(test.expErrs) > 0 && len(test.vals) != len(test.expErrs) {
			panic(fmt.Sprintf("invalid test at %d", i))
		}
		for j, val := range test.vals {
			t.Run(fmt.Sprintf("test%dval%d", i, j), func(t *testing.T) {
				pval, err := test.field.PilosafyVal(val)
				if len(test.exps) > 0 {
					errorIfNotEqual(t, test.exps[j], pval)
				}
				if len(test.expErrs) > 0 {
					errorIfNotErrMatch(t, test.expErrs[j], err)
				}
			})
		}
	}
}

func TestTTLOf(t *testing.T) {
	tests := []struct {
		name        string
		field       Field
		expectedTTL time.Duration
		expectedErr string
	}{
		{
			name:        "ttl StringField",
			field:       StringField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: "1s"},
			expectedTTL: time.Second,
			expectedErr: "",
		},
		{
			name:        "ttl IDField",
			field:       IDField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: "60s"},
			expectedTTL: time.Second * 60,
			expectedErr: "",
		},
		{
			name:        "ttl StringArrayField",
			field:       StringArrayField{NameVal: "a", Quantum: "YMD", TTL: "1h"},
			expectedTTL: time.Hour,
			expectedErr: "",
		},
		{
			name:        "ttl IDArrayField",
			field:       StringArrayField{NameVal: "a", Quantum: "YMD", TTL: "1h"},
			expectedTTL: time.Hour,
			expectedErr: "",
		},
		{
			name:        "ttl bad format",
			field:       StringField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: "0ssss"},
			expectedTTL: 0,
			expectedErr: "unable to parse TTL",
		},
		{
			name:        "ttl empty",
			field:       StringField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: ""},
			expectedTTL: 0,
			expectedErr: "",
		},
		{
			name:        "no ttl",
			field:       StringField{NameVal: "a", Mutex: false, Quantum: "YMD"},
			expectedTTL: 0,
			expectedErr: "",
		},
		{
			name:        "ttl IntField",
			field:       IntField{NameVal: "a"},
			expectedTTL: 0,
			expectedErr: "",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ttl, err := TTLOf(test.field)
			if ttl != test.expectedTTL {
				t.Errorf("expected TTL: '%v', got: '%v'", test.expectedTTL, ttl)
			}
			if err != nil && !strings.Contains(err.Error(), test.expectedErr) {
				t.Errorf("expected error: '%s', got: '%s'", test.expectedErr, err.Error())
			}
		})
	}

}

func TestTimestampFieldValidateDuration(t *testing.T) {
	errTimeOutsideEpoch := "value + epoch is too far from Unix epoch"
	type args struct {
		dur    int64
		offset int64
		unit   Unit
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
		errStr  string
	}{
		{
			name: "max-max",
			args: args{
				dur:    0,
				offset: TimestampToVal(Unit("s"), MaxTimestamp),
				unit:   Unit("s"),
			},
			wantErr: false,
		},
		{
			name: "oor-max",
			args: args{
				dur:    1,
				offset: TimestampToVal(Unit("s"), MaxTimestamp),
				unit:   Unit("s"),
			},
			wantErr: true,
			errStr:  errTimeOutsideEpoch,
		},
		{
			name: "max-oor",
			args: args{
				dur:    0,
				offset: TimestampToVal(Unit("s"), MaxTimestamp.Add(1*time.Second)),
				unit:   Unit("s"),
			},
			wantErr: true,
			errStr:  errTimeOutsideEpoch,
		},
		{
			name: "min-min",
			args: args{
				dur:    0,
				offset: TimestampToVal(Unit("s"), MinTimestamp),
				unit:   Unit("s"),
			},
			wantErr: false,
		},
		{
			name: "oor-min",
			args: args{
				dur:    -1,
				offset: TimestampToVal(Unit("s"), MinTimestamp),
				unit:   Unit("s"),
			},
			wantErr: true,
			errStr:  errTimeOutsideEpoch,
		},
		{
			name: "min-oor",
			args: args{
				dur:    0,
				offset: TimestampToVal(Unit("s"), MinTimestamp.Add(-1*time.Second)),
				unit:   Unit("s"),
			},
			wantErr: true,
			errStr:  errTimeOutsideEpoch,
		},
		{
			name: "max-max",
			args: args{
				dur:    0,
				offset: TimestampToVal(Unit("ns"), MaxTimestampNano),
				unit:   Unit("ns"),
			},
			wantErr: false,
		},
		{
			name: "oor-max",
			args: args{
				dur:    1,
				offset: TimestampToVal(Unit("ns"), MaxTimestampNano),
				unit:   Unit("ns"),
			},
			wantErr: true,
			errStr:  errTimeOutsideEpoch,
		},
		{
			name: "max-oor",
			args: args{
				dur:    0,
				offset: TimestampToVal(Unit("ns"), MaxTimestampNano.Add(1*time.Nanosecond)),
				unit:   Unit("ns"),
			},
			wantErr: true,
			errStr:  errTimeOutsideEpoch,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := validateDuration(tt.args.dur, tt.args.offset, tt.args.unit); (err != nil) != tt.wantErr {
				if !strings.Contains(err.Error(), tt.errStr) {
					t.Errorf("TimestampField.validateDuration() error = %v, wantErr %v", err, tt.wantErr)
				}
			}
		})
	}
}

func TestValidateTimestamp(t *testing.T) {
	type args struct {
		unit Unit
		ts   time.Time
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "nano-max",
			args: args{
				unit: Unit("ns"),
				ts:   MaxTimestampNano,
			},
			wantErr: false,
		},
		{
			name: "nano-min",
			args: args{
				unit: Unit("ns"),
				ts:   MinTimestampNano,
			},
			wantErr: false,
		},
		{
			name: "nano-max-oor",
			args: args{
				unit: Unit("ns"),
				ts:   MaxTimestampNano.Add(1 * time.Nanosecond),
			},
			wantErr: true,
		},
		{
			name: "nano-min-oor",
			args: args{
				unit: Unit("ns"),
				ts:   MinTimestampNano.Add(-1 * time.Nanosecond),
			},
			wantErr: true,
		},
		{
			name: "ms-max",
			args: args{
				unit: Unit("ms"),
				ts:   MaxTimestamp,
			},
			wantErr: false,
		},
		{
			name: "ms-min",
			args: args{
				unit: Unit("ms"),
				ts:   MinTimestamp,
			},
			wantErr: false,
		},
		{
			name: "ms-max-oor",
			args: args{
				unit: Unit("ms"),
				ts:   MaxTimestamp.Add(1 * time.Millisecond),
			},
			wantErr: true,
		},
		{
			name: "ms-min-oor",
			args: args{
				unit: Unit("ms"),
				ts:   MinTimestamp.Add(-1 * time.Millisecond),
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := validateTimestamp(tt.args.unit, tt.args.ts); (err != nil) != tt.wantErr {
				t.Errorf("validateTimestamp() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}
