package helpers

import (
	"context"
	erro "gardening/src/lib/error"
	"reflect"
	"testing"
)

func SetupTest(service interface{}, testing *testing.T) context.Context {
	ctx, canister, container := NewIsolationTesterScene(testing)

	canisterReflected := reflect.ValueOf(canister).Elem()
	containerReflected := reflect.ValueOf(container).Elem()
	serviceReflected := reflect.ValueOf(service)

	if serviceReflected.Kind() != reflect.Pointer || serviceReflected.Elem().Kind() != reflect.Struct {
		panic(erro.N("Should be a pointer to a struct"))
	}

	indService := serviceReflected.Elem()
	serviceName := indService.Type().Name()

	for j := 0; j < indService.NumField(); j++ {
		injected := indService.Field(j)
		fieldName := indService.Type().Field(j).Name
		injectInjected(fieldName, injected, serviceName, canisterReflected, containerReflected)
	}

	return ctx
}

func injectInjected(name string, injected reflect.Value, serviceName string, canisterReflected reflect.Value, containerReflected reflect.Value) {
	injectedKind := injected.Kind()

	// Struct are allowed to contain shared state values
	if injectedKind == reflect.Struct {
		return
	}

	isInterface := injectedKind == reflect.Interface
	isPointer := injectedKind == reflect.Pointer
	pointerOrInterface := isInterface || isPointer
	if !pointerOrInterface {
		panic(erro.N(name + " of " + serviceName + " should be a pointer or interface"))
	}

	value := findValueToInject(name, serviceName, canisterReflected, containerReflected)
	injected.Set(value)
}

func findValueToInject(name string, serviceName string, canister reflect.Value, container reflect.Value) reflect.Value {
	containerValue := container.FieldByName(name)
	if containerValue.IsValid() {
		return containerValue
	}
	canisterValue := canister.FieldByName(name)
	if canisterValue.IsValid() {
		return canisterValue
	}
	panic(erro.N(name + " service of " + serviceName + " has not been found neither in the container nor the canister"))
}
