package testing_helpers

import (
	"fmt"
	"net/netip"
	"reflect"
)

type PopulateFields struct {
	CustomFieldSetters map[reflect.Type]func(this *PopulateFields, field reflect.Value) (int, error)
	TagsToSkip         map[string]string
}

func NewPopulateFields() *PopulateFields {
	return &PopulateFields{CustomFieldSetters: defaultCustomFieldSetters(), TagsToSkip: make(map[string]string)}
}

func (p *PopulateFields) WithCustomFieldSetter(t reflect.Type, f func(this *PopulateFields, field reflect.Value) (int, error)) *PopulateFields {
	p.CustomFieldSetters[t] = f
	return p
}

func (p *PopulateFields) WithSkippedTag(tag, value string) *PopulateFields {
	p.TagsToSkip[tag] = value
	return p
}

func (p *PopulateFields) PopulateAll(v reflect.Value) (int, error) {
	typ := v.Type()
	totalExportedFields := 0
	for i := 0; i < typ.NumField(); i++ {
		f := typ.Field(i)
		if f.PkgPath != "" { // unexported
			continue
		}

		if p.skippedTagPresent(f.Tag) {
			continue
		}

		numOfExportedFields, err := p.setNonZero(v.Field(i))
		totalExportedFields += numOfExportedFields
		if err != nil {
			return totalExportedFields, err
		}
	}
	return totalExportedFields, nil
}

// setNonZero assigns a deterministic non-zero value to a field based on its kind,
// recursing into nested structs and populating one element of slice fields.
func (p *PopulateFields) setNonZero(field reflect.Value) (int, error) {
	if f, ok := p.CustomFieldSetters[field.Type()]; ok {
		return f(p, field)
	}

	switch field.Kind() {
	case reflect.String:
		field.SetString("non-zero")
	case reflect.Bool:
		field.SetBool(true)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		field.SetInt(7)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		field.SetUint(7)
	case reflect.Float32, reflect.Float64:
		field.SetFloat(7)
	case reflect.Struct:
		n, err := p.PopulateAll(field)
		return n + 1, err
	case reflect.Slice:
		s := reflect.MakeSlice(field.Type(), 1, 1)
		_, err := p.setNonZero(s.Index(0))
		if err != nil {
			return 0, err
		}
		field.Set(s)
	default:
		return 0, fmt.Errorf("unhandled field kind %s; extend setNonZero", field.Kind())
	}

	return 1, nil
}

func defaultCustomFieldSetters() map[reflect.Type]func(this *PopulateFields, field reflect.Value) (int, error) {
	return map[reflect.Type]func(this *PopulateFields, field reflect.Value) (int, error){
		reflect.TypeOf(netip.Prefix{}): func(_ *PopulateFields, field reflect.Value) (int, error) {
			field.Set(reflect.ValueOf(netip.MustParsePrefix("10.0.0.0/24")))
			return 1, nil
		},
	}
}

func (p *PopulateFields) skippedTagPresent(t reflect.StructTag) bool {
	for tag, value := range p.TagsToSkip {
		if v := t.Get(tag); v == value {
			return true
		}
	}
	return false
}
