package dag

import (
	"fmt"
	"reflect"
	"regexp"
)

const tagKey = "inject"

type injector[T Env] struct {
	data T
}

func NewInjector[T Env](data T) Injector {
	return &injector[T]{
		data: data,
	}
}

func (in *injector[T]) Inject(c interface{}) error {
	dataType := reflect.TypeOf(in.data).Elem()
	if dataType.Kind() != reflect.Struct {
		return fmt.Errorf("invaild injector, data %T is not a pointer to struct", in.data)
	}

	cv := reflect.ValueOf(c)
	if cv.Kind() != reflect.Pointer || cv.IsNil() {
		return fmt.Errorf("invaild case, %T is not a pointer", c)
	}
	caseVal := cv.Elem()

	caseType := caseVal.Type()
	if caseType.Kind() != reflect.Struct {
		return fmt.Errorf("invaild case, %T is not a pointer to struct", c)
	}

	dataVal := reflect.ValueOf(in.data).Elem()

	for i := 0; i < caseVal.NumField(); i++ {
		field := caseType.Field(i)
		if field.Anonymous || !field.IsExported() {
			continue
		}

		val, ok := field.Tag.Lookup(tagKey)
		// source env is specified but not this one
		if ok {
			if !regexp.MustCompile(val).Match([]byte(in.data.Name())) {
				continue
			}
		}
		fieldVal := caseVal.Field(i)

		if !fieldVal.CanSet() {
			return fmt.Errorf("field %s cannot be set", field.Name)
		}
		dataFieldVal := dataVal.FieldByName(field.Name)
		if !dataFieldVal.IsValid() {
			continue
		}

		if !dataFieldVal.Type().AssignableTo(field.Type) {
			return fmt.Errorf("field %s cannot be assigned, current type %s, assigned type %s", field.Name, field.Type, dataFieldVal.Type())
		}
		if !fieldVal.IsZero() && !fieldVal.Equal(dataFieldVal) {
			return fmt.Errorf(`field %s cannot be re-assigned multiple times, you can use inject:"envRegexp" to specify the source env`, field.Name)
		}
		fieldVal.Set(dataFieldVal)
	}

	return nil
}
