package gofakeit

import (
	"errors"
	"fmt"
	"reflect"
)

// Fakeable is an interface that can be implemented by a type to provide a custom fake value.
type Fakeable interface {
	// Fake returns a fake value for the type.
	Fake(faker *Faker) (any, error)
}

func isFakeable(t reflect.Type) bool {
	fakeableTyp := reflect.TypeOf((*Fakeable)(nil)).Elem()

	return t.Implements(fakeableTyp) || reflect.PtrTo(t).Implements(fakeableTyp)
}

func callFake(faker *Faker, v reflect.Value, possibleKinds ...reflect.Kind) (any, error) {
	f, ok := v.Addr().Interface().(Fakeable)
	if !ok {
		return nil, errors.New("not a Fakeable type")
	}

	fakedValue, err := f.Fake(faker)
	if err != nil {
		return nil, fmt.Errorf("error calling Fake: %w", err)
	}
	k := reflect.TypeOf(fakedValue).Kind()
	if !containsKind(possibleKinds, k) {
		return nil, fmt.Errorf("returned value kind %q is not amongst the valid ones: %v", k, possibleKinds)
	}

	switch k {
	case reflect.String:
		return reflect.ValueOf(fakedValue).String(), nil
	case reflect.Bool:
		return reflect.ValueOf(fakedValue).Bool(), nil
	case reflect.Int:
		return int(reflect.ValueOf(fakedValue).Int()), nil
	case reflect.Int8:
		return int8(reflect.ValueOf(fakedValue).Int()), nil
	case reflect.Int16:
		return int16(reflect.ValueOf(fakedValue).Int()), nil
	case reflect.Int32:
		return int32(reflect.ValueOf(fakedValue).Int()), nil
	case reflect.Int64:
		return int64(reflect.ValueOf(fakedValue).Int()), nil
	case reflect.Uint:
		return uint(reflect.ValueOf(fakedValue).Uint()), nil
	case reflect.Uint8:
		return uint8(reflect.ValueOf(fakedValue).Uint()), nil
	case reflect.Uint16:
		return uint16(reflect.ValueOf(fakedValue).Uint()), nil
	case reflect.Uint32:
		return uint32(reflect.ValueOf(fakedValue).Uint()), nil
	case reflect.Uint64:
		return uint64(reflect.ValueOf(fakedValue).Uint()), nil
	case reflect.Float32:
		return float32(reflect.ValueOf(fakedValue).Float()), nil
	case reflect.Float64:
		return float64(reflect.ValueOf(fakedValue).Float()), nil
	case reflect.Slice:
		return reflect.ValueOf(fakedValue).Interface(), nil
	case reflect.Map:
		return reflect.ValueOf(fakedValue).Interface(), nil
	case reflect.Struct:
		return reflect.ValueOf(fakedValue).Interface(), nil
	default:
		return nil, fmt.Errorf("unsupported type %q", k)
	}
}

func containsKind(possibleKinds []reflect.Kind, kind reflect.Kind) bool {
	for _, k := range possibleKinds {
		if k == kind {
			return true
		}
	}
	return false
}
