package string_utils

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"unicode"

	"golang.org/x/text/runes"
	"golang.org/x/text/transform"
	"golang.org/x/text/unicode/norm"
)

// ReplaceNonSpacingMarks removes diacritics e.g. êžů becomes ezu
func ReplaceNonSpacingMarks(str string) string {
	t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) // Mn: non-spacing marks
	result, _, _ := transform.String(t, str)
	return result
}

func RemoveAllWhiteSpaces(s string) string {
	return strings.ReplaceAll(strings.ReplaceAll(s, " ", ""), "\t", "")
}

func ReplaceCaseInsensitive(string, toReplace, replaceWith string) string {
	regex := regexp.MustCompile("(?i)" + strings.ToLower(toReplace))
	return regex.ReplaceAllString(string, replaceWith)
}

// TrimQuotes - trims quotes from a string (ie: "foo" will return foo)
func TrimQuotes(stringToTrim string) string {
	if len(stringToTrim) >= 2 {
		if stringToTrim[0] == '"' && stringToTrim[len(stringToTrim)-1] == '"' {
			return stringToTrim[1 : len(stringToTrim)-1]
		}
	}

	return stringToTrim
}

// IsNumericString returns true if the string can be converted to a float without error
func IsNumericString(s string) bool {
	_, err := strconv.ParseFloat(s, 64)
	return err == nil
}

// StandardisePhoneNumber standardises phone numbers with +27 instead of 0 prefix
func StandardisePhoneNumber(number string) string {
	// is the first rune/char of the string a 0
	if []rune(number)[0] == []rune("0")[0] {
		// Add south african country code (hardcoded for now)
		number = "+27" + number[1:]
	}
	return number
}

func FormatPhoneNumber(phoneNumber string) string {
	if len(phoneNumber) > 7 {
		return phoneNumber
	}

	// Format as 076 453 2188
	phoneNumber = insertInto(phoneNumber, 3, " ")
	phoneNumber = insertInto(phoneNumber, 7, " ")
	return phoneNumber
}

func insertInto(s string, index int, characters string) string {
	return s[:index] + characters + s[index:]
}

func IsAlphaNumeric(str string) bool {
	regex := regexp.MustCompile("^[a-zA-Z0-9]*$")
	return regex.MatchString(str)
}

func IsAlphaNumericOrDash(str string) bool {
	regex := regexp.MustCompile("^[a-zA-Z0-9-]*$")
	return regex.MatchString(str)
}

func Equal(a string, b string) bool {
	return strings.TrimSpace(strings.ToLower(a)) == strings.TrimSpace(strings.ToLower(b))
}

func UnwrapString(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

// TrimP trims specified strings, replacing empty string with nil
func TrimP(sp *string) *string {
	if sp == nil {
		return nil
	}
	s := strings.TrimSpace(*sp)
	if s == "" {
		return nil
	}
	return &s
}

// ConcatP concatenates all specified non-empty strings with ", " separators
func ConcatP(args ...*string) string {
	s := ""
	for _, arg := range args {
		if arg != nil && *arg != "" {
			if s != "" {
				s += ", "
			}
			s += *arg
		}
	}
	return s
}

func ToJSONString(object interface{}) (string, error) {
	jsonBytes, err := json.Marshal(&object)
	if err != nil {
		return "", err
	}
	return string(jsonBytes), nil
}

func Int64ToString(number int64) string {
	return strconv.FormatInt(number, 10)
}

func IntToString(number int) string {
	return strconv.Itoa(number)
}
func StringToInt(stringValue string) (int, error) {
	return strconv.Atoi(stringValue)
}

func Int64SliceToString(numbers []int64) string {
	numString := fmt.Sprint(numbers)
	numString = strings.Join(strings.Split(numString, " "), ",")
	return numString
}

func StringToInt64(stringValue string) (int64, error) {
	number, err := strconv.ParseInt(stringValue, 10, 64)
	return number, err
}

func StringToFloat64(stringValue string) (float64, error) {
	number, err := strconv.ParseFloat(stringValue, 64)
	if err != nil {
		return 0, err
	}
	return number, nil
}

func Float64ToString(number float64, precision int) string {
	return strconv.FormatFloat(number, 'f', precision, 64)
}

func Float64ToStringWithPrec(number float64, prec int) string {
	return strconv.FormatFloat(number, 'f', prec, 64)
}

func ValidateStringAsInt64(stringValue string) error {
	_, err := StringToInt64(stringValue)
	return err
}

func PtoString(stringPointer *string) string {
	if stringPointer == nil {
		return ""
	}

	return *stringPointer
}

func IsEmpty(sp *string) bool {
	if sp == nil {
		return true
	}

	return len(*sp) == 0
}

// StringTrimQuotes - trims quotes from a string (ie: "foo" will return foo)
func StringTrimQuotes(stringToTrim string) string {
	if len(stringToTrim) >= 2 {
		if stringToTrim[0] == '"' && stringToTrim[len(stringToTrim)-1] == '"' {
			return stringToTrim[1 : len(stringToTrim)-1]
		}
	}

	return stringToTrim
}

func KeyToHumanReadable(s string) string {
	s = strings.TrimSpace(s)

	re := regexp.MustCompile("(_|-)")
	s = re.ReplaceAllString(s, " ")

	return sentenceCase(string(s))
}

func sentenceCase(str string) string {
	for i, v := range str {
		return string(unicode.ToUpper(v)) + str[i+1:]
	}
	return ""
}

// RemoveUrlScheme Removes http:// or https:// from a URL
func RemoveUrlScheme(str string) string {
	newStr := strings.Replace(str, "http://", "", 1)
	newStr = strings.Replace(str, "https://", "", 1)
	return newStr
}

// EscapeOpenSearchSearchString See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters
func EscapeOpenSearchSearchString(str string) string {
	searchString := str

	// Reserved characters
	// NOTE: first char must be "\" to prevent replacing it again after replacing other chars with "\\"
	reservedCharacters := []string{"\\", "+", "-", "=", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":", "/"}

	// Remove "<" and ">"
	strings.ReplaceAll(searchString, "<", "")
	strings.ReplaceAll(searchString, ">", "")

	// Escape the reserved characters with double backslashes ("\\")
	for _, char := range reservedCharacters {
		if strings.Contains(searchString, char) {
			re := regexp.MustCompile(char)
			searchString = re.ReplaceAllString(searchString, "\\"+char)
		}
	}
	return searchString
}