package audit

import (
	"reflect"
	"regexp"
	"strings"

	"github.com/r3labs/diff/v2"
	"gitlab.com/uafrica/go-utils/reflection"
	"gitlab.com/uafrica/go-utils/string_utils"
)

type FieldChange struct {
	From interface{} `json:"change_from"`
	To   interface{} `json:"change_to"`
}

func GetChanges(original interface{}, new interface{}) (map[string]interface{}, error) {
	changes := map[string]interface{}{}
	changelog, err := diff.Diff(original, new)
	if err != nil {
		return changes, err
	}

	for _, change := range changelog {

		if len(change.Path) == 1 {
			// Root object change
			field := ToSnakeCase(change.Path[0])
			changes[field] = FieldChange{
				From: change.From,
				To:   change.To,
			}
		} else if len(change.Path) == 2 {
			// Child object changed
			// ["Account", "ID"]
			// 0 = Object
			// 1 = field

			objectKey := ToSnakeCase(change.Path[0])
			field := ToSnakeCase(change.Path[1])

			existingObject, present := changes[objectKey]
			if present {
				if object, ok := existingObject.(map[string]interface{}); ok {
					object[field] = FieldChange{
						From: change.From,
						To:   change.To,
					}
					changes[objectKey] = object
				}
			} else {
				fieldChange := map[string]interface{}{
					field: FieldChange{
						From: change.From,
						To:   change.To,
					},
				}
				changes[objectKey] = fieldChange
			}

		} else if len(change.Path) == 3 {
			// Array of objects
			// ["Parcel", "0", "ActualWeight"]
			// 0 = Object
			// 1 = Index of object
			// 2 = field

			objectKey := ToSnakeCase(change.Path[0])
			indexString := change.Path[1]
			index, _ := string_utils.StringToInt64(indexString)
			field := ToSnakeCase(change.Path[2])

			arrayObject, present := changes[objectKey]
			if present {
				if arrayOfObjects, ok := arrayObject.([]map[string]interface{}); ok {
					if len(arrayOfObjects) > int(index) {
						// Add field to existing object in array
						object := arrayOfObjects[index]
						object[field] = FieldChange{
							From: change.From,
							To:   change.To,
						}
					} else {
						// new object, append to existing array
						fieldChange := map[string]interface{}{
							field: FieldChange{
								From: change.From,
								To:   change.To,
							},
						}
						changes[objectKey] = append(arrayOfObjects, fieldChange)
					}

				}
			} else {
				// Create array of objects
				fieldChange := map[string]interface{}{
					field: FieldChange{
						From: change.From,
						To:   change.To,
					},
				}
				changes[objectKey] = []map[string]interface{}{
					fieldChange,
				}
			}
		}
	}

	return changes, nil
}

var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")

func ToSnakeCase(str string) string {
	snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
	snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
	return strings.ToLower(snake)
}

func GetIntValue(object interface{}, key string) int64 {
	structValue := reflect.ValueOf(object)
	if structValue.Kind() == reflect.Struct {
		field := structValue.FieldByName(key)
		id := reflection.GetInt64Value(field)
		return id
	}
	return 0
}

func GetStringValue(object interface{}, key string) string {
	structValue := reflect.ValueOf(object)
	if structValue.Kind() == reflect.Struct {
		field := structValue.FieldByName(key)
		id := reflection.GetStringValue(field)
		return id
	}
	return ""
}