package steps

import (
	"context"
	"fmt"
	"reflect"

	"github.com/pingcap/log"
)

// update struct field by pointer
type updateStructFieldByPointer struct {
	StructPoint interface{}
	Field       string
	Value       interface{}
}

func (u *updateStructFieldByPointer) String() string {
	var val reflect.Value
	v := reflect.ValueOf(u.Value)
	switch v.Kind() {
	case reflect.Ptr:
		val = v.Elem()
	default:
		val = v
	}
	return fmt.Sprintf("Update struct %+v, set %s to %+v", u.StructPoint, u.Field, val)
}

func (u *updateStructFieldByPointer) Execute(ctx context.Context) error {
	log.Info("executing updateFields..")

	var val reflect.Value
	v := reflect.ValueOf(u.Value)
	switch v.Kind() {
	case reflect.Ptr:
		val = v.Elem()
	default:
		val = v
	}

	structValue := reflect.ValueOf(u.StructPoint).Elem()
	structFieldValue := structValue.FieldByName(u.Field)

	if !structFieldValue.IsValid() {
		return fmt.Errorf("no such field: %s in strucr", u.Field)
	}

	if !structFieldValue.CanSet() {
		return fmt.Errorf("cannot set %s field value", u.Field)
	}

	if structFieldValue.Type() != val.Type() {
		return fmt.Errorf("cannot set value type: %T to field type: %T", val, structFieldValue)
	}

	structFieldValue.Set(val)

	log.Info("update fields successful!")
	return nil
}

// UpdateStructFieldByPointer update struct field by pointer
func (b *Builder) UpdateStructFieldByPointer(structPointer interface{}, field string, value interface{}) *Builder {
	b.steps = append(b.steps, NewUpdateStructFieldByPointer(structPointer, field, value))
	return b
}

func NewUpdateStructFieldByPointer(structPointer interface{}, field string, value interface{}) Step {
	return &updateStructFieldByPointer{structPointer, field, value}
}
