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

// t_common_test.go
package expr

import (
	"errors"
	"os"
	"path"
	"runtime"
	"strings"
	"testing"

	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/expr/scan"
)

type inputType struct {
	source     string
	wantResult any
	wantErr    any
}

func getSourceName(startFrame int) (sourcename string) {
	for i := startFrame; i < 10; i++ {
		_, sourcepath, _, ok := runtime.Caller(i)
		if !ok {
			break
		}
		sourcename = path.Base(sourcepath)
		if sourcename != "t_common_test.go" {
			break
		}
	}
	return
}

func RunCtxTestSuiteSpec(t *testing.T, ctx kern.ExprContext, section string, inputs []inputType, spec ...int) {
	succeeded := 0
	failed := 0

	sourcename := getSourceName(2)
	t.Logf("Test suite %s from file %s", section, sourcename)

	for _, count := range spec {
		index := count - 1
		if index >= 0 && index < len(inputs) {
			good := doTest(t, ctx, section, &inputs[index], count)

			if good {
				succeeded++
			} else {
				failed++
			}
		} else {
			t.Errorf("%s -- test count: %d, invalid index", section, count)
			failed++
		}
	}
	t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}

func RunTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
	RunCtxTestSuiteSpec(t, nil, section, inputs, spec...)
}

func RunCtxTestSuite(t *testing.T, ctx kern.ExprContext, section string, inputs []inputType) {

	succeeded := 0
	failed := 0

	sourcename := getSourceName(2)

	t.Logf("Test suite %s from file %s", section, sourcename)

	for i, input := range inputs {
		// fmt.Printf("%3d: %s\n", i+1, input.source)

		good := doTest(t, ctx, section, &input, i+1)
		if good {
			succeeded++
		} else {
			failed++
		}
	}
	t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func RunTestSuite(t *testing.T, section string, inputs []inputType) {
	RunCtxTestSuite(t, nil, section, inputs)
}

func getWantedError(input *inputType) error {
	var wantErr error
	var ok bool
	if wantErr, ok = input.wantErr.(error); !ok {
		if msg, ok := input.wantErr.(string); ok {
			wantErr = errors.New(msg)
		}
	}
	return wantErr
}

func doTest(t *testing.T, ctx kern.ExprContext, section string, input *inputType, count int) (good bool) {
	var ast kern.Expr
	var gotResult any
	var gotErr error

	wantErr := getWantedError(input)

	parser := NewParser()
	if ctx == nil {
		ctx = NewSimpleStore()
	}

	logTest(t, count, section, input.source, input.wantResult, wantErr)

	r := strings.NewReader(input.source)
	scanner := scan.NewScanner(r, scan.DefaultTranslations())

	good = true
	if ast, gotErr = parser.Parse(scanner); gotErr == nil {
		gotResult, gotErr = ast.Eval(ctx)
	}

	if gotErr == nil {
		if wantErr == nil {
			eq := kern.Equal(gotResult, input.wantResult)

			if !eq /*gotResult != input.wantResult*/ {
				t.Errorf(">>>%s/%d: `%s` -> result = %v [%s], want = %v [%s]", section, count, input.source, gotResult, kern.TypeName(gotResult), input.wantResult, kern.TypeName(input.wantResult))
				good = false
			}
		} else {
			t.Errorf(">>>%s/%d: `%s` -> result = %v [%s], got-err = <nil>, expected-err = %v", section, count, input.source, gotResult, kern.TypeName(gotResult), wantErr)
			good = false
		}
	}

	if good && gotErr != wantErr {
		if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
			t.Errorf(">>>%s/%d: %s -> got-err = <%v>, expected-err = <%v>", section, count, input.source, gotErr, wantErr)
			good = false
		}
	}
	return
}

func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
	if wantErr == nil {
		t.Logf("[+]%s nr %3d -- `%s`  --> %v", section, n, source, wantResult)
	} else {
		t.Logf("[-]%s nr %3d -- `%s`  --> %v", section, n, source, wantErr)
	}
}

func testIteratorCallOp(t *testing.T, section string, it kern.Iterator) {
	if _, err := it.CallOperation("next", map[string]any{}); err != nil {
		t.Errorf(`%s -- CallOperation("next") failed: %v`, section, err)
	}
	if _, err := it.CallOperation("current", map[string]any{}); err != nil {
		t.Errorf(`%s -- CallOperation("current") failed: %v`, section, err)
	}
	if _, err := it.CallOperation("clean", map[string]any{}); err != nil {
		t.Errorf(`%s -- CallOperation("clean") failed: %v`, section, err)
	}
	if _, err := it.CallOperation("index", map[string]any{}); err != nil {
		t.Errorf(`%s -- CallOperation("index") failed: %v`, section, err)
	}
	if _, err := it.CallOperation("count", map[string]any{}); err != nil {
		t.Errorf(`%s -- CallOperation("count") failed: %v`, section, err)
	}
	if _, err := it.CallOperation("reset", map[string]any{}); err != nil {
		t.Errorf(`%s -- CallOperation("reset") failed: %v`, section, err)
	}
	if _, err := it.CallOperation("fake", map[string]any{}); err == nil {
		t.Errorf(`%s -- CallOperation("fake") should return error`, section)
	}
}

// Test utilities

func fileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return !errors.Is(err, os.ErrNotExist)
}
