// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// t_utils_test.go
package expr

import (
	"errors"
	"reflect"
	"testing"

	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/expr/types"
	"git.portale-stac.it/go-pkg/expr/types/array"
	"git.portale-stac.it/go-pkg/expr/types/boolean"
	"git.portale-stac.it/go-pkg/expr/types/float"
	"git.portale-stac.it/go-pkg/expr/types/str"
	"git.portale-stac.it/go-pkg/expr/util"
)

func TestIsString(t *testing.T) {
	count := 0
	succeeded := 0
	failed := 0

	count++
	if !boolean.IsBool(true) {
		t.Errorf("%d: IsBool(true) -> result = false, want true", count)
		failed++
	} else {
		succeeded++
	}

	count++
	if !str.IsString("abc") {
		t.Errorf("%d: IsString(\"abc\") -> result = false, want true", count)
		failed++
	} else {
		succeeded++
	}

	count++
	if !types.IsInteger(int64(123)) {
		t.Errorf("%d: IsInteger(123) -> result = false, want true", count)
		failed++
	} else {
		succeeded++
	}

	count++
	if !float.IsFloat(1.23) {
		t.Errorf("%d: IsFloat(1.23) -> result = false, want true", count)
		failed++
	} else {
		succeeded++
	}

	count++
	if !float.IsFloat(types.NumAsFloat(123)) {
		t.Errorf("%d: IsFloat(numAsFloat(123)) -> result = false, want true", count)
		failed++
	} else {
		succeeded++
	}

	count++
	if kern.IsIterator("fake") {
		t.Errorf(`%d: IsIterator("fake") -> result = true, want false`, count)
		failed++
	} else {
		succeeded++
	}

	count++
	b, ok := boolean.ToBool(true)
	if !(ok && b) {
		t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
		failed++
	} else {
		succeeded++
	}

	count++
	b, ok = boolean.ToBool("abc")
	if !(ok && b) {
		t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
		failed++
	} else {
		succeeded++
	}

	count++
	b, ok = boolean.ToBool([]int{})
	if ok {
		t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
		failed++
	} else {
		succeeded++
	}

	t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
}

func TestToIntOk(t *testing.T) {
	source := int64(64)
	wantValue := int(64)
	wantErr := error(nil)

	gotValue, gotErr := types.ToGoInt(source, "test")

	if gotErr != nil || gotValue != wantValue {
		t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
			source, gotValue, gotErr, wantValue, wantErr)
	}
}

func TestToIntErr(t *testing.T) {
	source := uint64(64)
	wantValue := 0
	wantErr := errors.New(`test expected integer, got uint64 (64)`)

	gotValue, gotErr := types.ToGoInt(source, "test")

	if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
		t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
			source, gotValue, gotErr, wantValue, wantErr)
	}
}

func TestToInt64Ok(t *testing.T) {
	source := int64(64)
	wantValue := int64(64)
	wantErr := error(nil)

	gotValue, gotErr := types.ToGoInt64(source, "test")

	if gotErr != nil || gotValue != wantValue {
		t.Errorf("toInt64(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
			source, gotValue, gotErr, wantValue, wantErr)
	}
}

func TestToInt64Err(t *testing.T) {
	source := uint64(64)
	wantValue := int64(0)
	wantErr := errors.New(`test expected integer, got uint64 (64)`)

	gotValue, gotErr := types.ToGoInt64(source, "test")

	if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
		t.Errorf("toInt64(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
			source, gotValue, gotErr, wantValue, wantErr)
	}
}

func TestAnyInteger(t *testing.T) {
	type inputType struct {
		source    any
		wantValue int64
		wantOk    bool
	}
	section := "utils.anyInteger"

	inputs := []inputType{
		/*  1 */ {int8(-8), int64(-8), true},
		/*  2 */ {int16(-16), int64(-16), true},
		/*  3 */ {int32(-32), int64(-32), true},
		/*  4 */ {int64(-64), int64(-64), true},
		/*  5 */ {uint8(8), int64(8), true},
		/*  6 */ {uint16(16), int64(16), true},
		/*  7 */ {uint32(32), int64(32), true},
		/*  8 */ {uint64(64), int64(64), true},
		/*  9 */ {int(-1), int64(-1), true},
		/* 10 */ {true, 0, false},
	}

	succeeded := 0
	failed := 0

	for i, input := range inputs {
		gotValue, gotOk := types.AnyInteger(input.source)
		if gotOk != input.wantOk || gotValue != input.wantValue {
			t.Errorf("%d: anyInteger(%v) -> gotOk = %t, wantOk = %t; gotValue = %v, wantValue = %v",
				i+1, input.source, gotOk, input.wantOk, gotValue, input.wantValue)
			failed++
		} else {
			succeeded++
		}
	}
	t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}

func TestCopyMap(t *testing.T) {
	source := map[string]int{"one": 1, "two": 2, "three": 3}
	dest := make(map[string]int)
	result := util.CopyMap(dest, source)
	if !reflect.DeepEqual(result, source) {
		t.Errorf("utils.CopyMap() failed")
	}
}

func TestToStringOk(t *testing.T) {
	source := "ciao"
	wantValue := "ciao"
	wantErr := error(nil)

	gotValue, gotErr := str.ToGoString(source, "test")

	if gotErr != nil {
		t.Error(gotErr)
	} else if gotValue != wantValue {
		t.Errorf("toGoString(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
			source, gotValue, gotErr, wantValue, wantErr)
	}
}

func TestToStringErr(t *testing.T) {
	source := array.NewArrayA()
	wantValue := ""
	wantErr := errors.New(`test expected string, got array ([])`)

	gotValue, gotErr := str.ToGoString(source, "test")

	if gotErr != nil && gotErr.Error() != wantErr.Error() {
		t.Errorf(`ToGoString(%v, "test") gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
			source, gotValue, gotErr, wantValue, wantErr)
	} else if gotValue != wantValue {
		t.Errorf(`ToString(%v, "test") gotValue=%q, gotErr=%v -> wantValue=%q, wantErr=%v`,
			source, gotValue, gotErr, wantValue, wantErr)
	}
}

func TestIsFunctorSucc(t *testing.T) {
	f := kern.NewGolangFunctor(isNilFunc)
	if !kern.IsFunctor(f) {
		t.Errorf("isNilFunc() evalued as not a functor")
	}
}

func TestIsFunctorFail(t *testing.T) {
	f := int64(1)
	if kern.IsFunctor(f) {
		t.Errorf("int evalued as a functor")
	}
}
