package config

import (
	"encoding/json"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"gitlab.com/uafrica/go-utils/errors"
)

func Load(prefix string, configPtr interface{}) error {
	if !prefixRegex.MatchString(prefix) {
		return errors.Errorf("invalid config prefix \"%s\"", prefix)
	}
	if configPtr == nil {
		return errors.Errorf("Load(nil)")
	}
	t := reflect.TypeOf(configPtr)
	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
		return errors.Errorf("%T is not &struct", configPtr)
	}
	v := reflect.ValueOf(configPtr)
	if err := load(prefix, t.Elem(), v.Elem()); err != nil {
		return errors.Wrapf(err, "failed to load config with prefix %s", prefix)
	}
	if validator, ok := configPtr.(Validator); ok {
		if err := validator.Validate(); err != nil {
			return errors.Wrapf(err, "invalid config with prefix %s", prefix)
		}
	}
	return nil
}

type nameValue struct {
	name  string
	value string
}

func load(prefix string, t reflect.Type, ptrValue reflect.Value) error {
	switch t.Kind() {
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i)
			if err := load(prefix+"_"+strings.ToUpper(f.Name), f.Type, ptrValue.Field(i)); err != nil {
				return errors.Wrapf(err, "cannot load field")
			}
		}

	case reflect.Slice:
		//expect JSON list of values or just one value
		s := os.Getenv(prefix)
		if s != "" {
			if err := json.Unmarshal([]byte(s), ptrValue.Addr().Interface()); err != nil {
				return errors.Wrapf(err, "cannot read env %s=%s into %s", prefix, s, t.Name())
			}
		} else {
			//see if _1, _2, ... is used then construct a list with those values
			//(only applies to list of strings)
			values := map[string]string{}
			for _, x := range os.Environ() {
				parts := strings.SplitN(x, "=", 2)
				if len(parts) == 2 && strings.HasPrefix(parts[0], prefix+"_") {
					values[parts[0]] = parts[1]
				}
			}
			if len(values) > 0 {
				//add in sorted order
				list := []nameValue{}
				for n, v := range values {
					list = append(list, nameValue{name: n, value: v})
				}
				sort.Slice(list, func(i, j int) bool {
					return list[i].name < list[j].name
				})
				s := ""
				for _, nv := range list {
					if t.Elem().Kind() == reflect.String {
						s += ",\"" + nv.value + "\"" //quoted
					} else {
						s += "," + nv.value //unquoted
					}
				}
				s = "[" + s[1:] + "]"
				if err := json.Unmarshal([]byte(s), ptrValue.Addr().Interface()); err != nil {
					return errors.Wrapf(err, "cannot read env %s=%s into %s", prefix, s, t.Name())
				}
			}
		}

	case reflect.String:
		s := os.Getenv(prefix)
		if s != "" {
			ptrValue.Set(reflect.ValueOf(s))
		}

	case reflect.Int64:
		s := os.Getenv(prefix)
		if s != "" {
			i64, err := strconv.ParseInt(s, 10, 64)
			if err != nil {
				return errors.Errorf("%s=%s not integer value", prefix, s)
			}
			ptrValue.Set(reflect.ValueOf(i64))
		}

	default:
		return errors.Errorf("cannot load config %s_... into %s kind %s", prefix, t.Name(), t.Kind())
	}
	return nil
}

const prefixPattern = `[A-Z]([A-Z0-9_]*[A-Z0-9])*`

var prefixRegex = regexp.MustCompile("^" + prefixPattern + "$")