package gofakeit

import (
	crand "crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"math"
	"math/rand"
	"reflect"
	"strings"
	"unicode"

	"github.com/brianvoe/gofakeit/v6/data"
)

const lowerStr = "abcdefghijklmnopqrstuvwxyz"
const upperStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const numericStr = "0123456789"
const specialStr = "@#$%&?|!(){}<>=*+-_:;,."
const specialSafeStr = "@#$&?!-_*."
const spaceStr = " "
const allStr = lowerStr + upperStr + numericStr + specialStr + spaceStr
const vowels = "aeiou"
const hashtag = '#'
const questionmark = '?'
const dash = '-'
const base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
const minUint = 0
const maxUint = ^uint(0)
const minInt = -maxInt - 1
const maxInt = int(^uint(0) >> 1)

// Seed will set the global random value. Setting seed to 0 will use crypto/rand
func Seed(seed int64) {
	if seed == 0 {
		binary.Read(crand.Reader, binary.BigEndian, &seed)
		globalFaker.Rand.Seed(seed)
	} else {
		globalFaker.Rand.Seed(seed)
	}
}

// Check if in lib
func dataCheck(dataVal []string) bool {
	var checkOk bool

	if len(dataVal) == 2 {
		_, checkOk = data.Data[dataVal[0]]
		if checkOk {
			_, checkOk = data.Data[dataVal[0]][dataVal[1]]
		}
	}

	return checkOk
}

// Get Random Value
func getRandValue(r *rand.Rand, dataVal []string) string {
	if !dataCheck(dataVal) {
		return ""
	}
	return data.Data[dataVal[0]][dataVal[1]][r.Intn(len(data.Data[dataVal[0]][dataVal[1]]))]
}

// Replace # with numbers
func replaceWithNumbers(r *rand.Rand, str string) string {
	if str == "" {
		return str
	}
	bytestr := []byte(str)
	for i := 0; i < len(bytestr); i++ {
		if bytestr[i] == hashtag {
			bytestr[i] = byte(randDigit(r))
		}
	}
	if bytestr[0] == '0' {
		bytestr[0] = byte(r.Intn(8)+1) + '0'
	}

	return string(bytestr)
}

// Replace ? with ASCII lowercase letters
func replaceWithLetters(r *rand.Rand, str string) string {
	if str == "" {
		return str
	}
	bytestr := []byte(str)
	for i := 0; i < len(bytestr); i++ {
		if bytestr[i] == questionmark {
			bytestr[i] = byte(randLetter(r))
		}
	}

	return string(bytestr)
}

// Replace ? with ASCII lowercase letters between a and f
func replaceWithHexLetters(r *rand.Rand, str string) string {
	if str == "" {
		return str
	}
	bytestr := []byte(str)
	for i := 0; i < len(bytestr); i++ {
		if bytestr[i] == questionmark {
			bytestr[i] = byte(randHexLetter(r))
		}
	}

	return string(bytestr)
}

// Generate random lowercase ASCII letter
func randLetter(r *rand.Rand) rune {
	allLetters := upperStr + lowerStr
	return rune(allLetters[r.Intn(len(allLetters))])
}

func randCharacter(r *rand.Rand, s string) string {
	return string(s[r.Int63()%int64(len(s))])
}

// Generate random lowercase ASCII letter between a and f
func randHexLetter(r *rand.Rand) rune {
	return rune(byte(r.Intn(6)) + 'a')
}

// Generate random ASCII digit
func randDigit(r *rand.Rand) rune {
	return rune(byte(r.Intn(10)) + '0')
}

// Generate random integer between min and max
func randIntRange(r *rand.Rand, min, max int) int {
	// If they pass in the same number, just return that number
	if min == max {
		return min
	}

	// If they pass in a min that is bigger than max, swap them
	if min > max {
		ogmin := min
		min = max
		max = ogmin
	}

	// Figure out if the min/max numbers calculation
	// would cause a panic in the Int63() function.
	if max-min+1 > 0 {
		return min + int(r.Int63n(int64(max-min+1)))
	}

	// Loop through the range until we find a number that fits
	for {
		v := int(r.Uint64())
		if (v >= min) && (v <= max) {
			return v
		}
	}
}

// Generate random uint between min and max
func randUintRange(r *rand.Rand, min, max uint) uint {
	// If they pass in the same number, just return that number
	if min == max {
		return min
	}

	// If they pass in a min that is bigger than max, swap them
	if min > max {
		ogmin := min
		min = max
		max = ogmin
	}

	// Figure out if the min/max numbers calculation
	// would cause a panic in the Int63() function.
	if int(max)-int(min)+1 > 0 {
		return uint(r.Intn(int(max)-int(min)+1) + int(min))
	}

	// Loop through the range until we find a number that fits
	for {
		v := uint(r.Uint64())
		if (v >= min) && (v <= max) {
			return v
		}
	}
}

func toFixed(num float64, precision int) float64 {
	output := math.Pow(10, float64(precision))
	return float64(math.Floor(num*output)) / output
}

func equalSliceString(a, b []string) bool {
	sizeA, sizeB := len(a), len(b)
	if sizeA != sizeB {
		return false
	}

	for i, va := range a {
		vb := b[i]

		if va != vb {
			return false
		}
	}
	return true
}

func equalSliceInt(a, b []int) bool {
	sizeA, sizeB := len(a), len(b)
	if sizeA != sizeB {
		return false
	}

	for i, va := range a {
		vb := b[i]

		if va != vb {
			return false
		}
	}
	return true
}

func equalSliceInterface(a, b []any) bool {
	sizeA, sizeB := len(a), len(b)
	if sizeA != sizeB {
		return false
	}

	for i, va := range a {
		if !reflect.DeepEqual(va, b[i]) {
			return false
		}
	}
	return true
}

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func anyToString(a any) string {
	if a == nil {
		return ""
	}

	// If it's a slice of bytes or struct, unmarshal it into an interface
	if bytes, ok := a.([]byte); ok {
		return string(bytes)
	}

	// If it's a struct, map, or slice, convert to JSON
	switch reflect.TypeOf(a).Kind() {
	case reflect.Struct, reflect.Map, reflect.Slice:
		b, err := json.Marshal(a)
		if err == nil {
			return string(b)
		}
	}

	return fmt.Sprintf("%v", a)
}

// Title returns a copy of the string s with all Unicode letters that begin words
// mapped to their Unicode title case
func title(s string) string {
	// isSeparator reports whether the rune could mark a word boundary
	isSeparator := func(r rune) bool {
		// ASCII alphanumerics and underscore are not separators
		if r <= 0x7F {
			switch {
			case '0' <= r && r <= '9':
				return false
			case 'a' <= r && r <= 'z':
				return false
			case 'A' <= r && r <= 'Z':
				return false
			case r == '_':
				return false
			}
			return true
		}

		// Letters and digits are not separators
		if unicode.IsLetter(r) || unicode.IsDigit(r) {
			return false
		}

		// Otherwise, all we can do for now is treat spaces as separators.
		return unicode.IsSpace(r)
	}

	prev := ' '
	return strings.Map(
		func(r rune) rune {
			if isSeparator(prev) {
				prev = r
				return unicode.ToTitle(r)
			}
			prev = r
			return r
		},
		s)
}

func funcLookupSplit(str string) []string {
	out := []string{}
	for str != "" {
		if strings.HasPrefix(str, "[") {
			startIndex := strings.Index(str, "[")
			endIndex := strings.Index(str, "]")
			val := str[(startIndex) : endIndex+1]
			out = append(out, strings.TrimSpace(val))
			str = strings.Replace(str, val, "", 1)

			// Trim off comma if it has it
			if strings.HasPrefix(str, ",") {
				str = strings.Replace(str, ",", "", 1)
			}
		} else {
			strSplit := strings.SplitN(str, ",", 2)
			strSplitLen := len(strSplit)
			if strSplitLen >= 1 {
				out = append(out, strings.TrimSpace(strSplit[0]))
			}
			if strSplitLen >= 2 {
				str = strSplit[1]
			} else {
				str = ""
			}
		}
	}

	return out
}

// Used for parsing the tag in a struct
func parseNameAndParamsFromTag(tag string) (string, string) {
	// Trim the curly on the beginning and end
	tag = strings.TrimLeft(tag, "{")
	tag = strings.TrimRight(tag, "}")
	// Check if has params separated by :
	fNameSplit := strings.SplitN(tag, ":", 2)
	fName := ""
	fParams := ""
	if len(fNameSplit) >= 1 {
		fName = fNameSplit[0]
	}
	if len(fNameSplit) >= 2 {
		fParams = fNameSplit[1]
	}
	return fName, fParams
}

// Used for parsing map params
func parseMapParams(info *Info, fParams string) *MapParams {
	// Get parameters, make sure params and the split both have values
	mapParams := NewMapParams()
	paramsLen := len(info.Params)

	// If just one param and its a string simply just pass it
	if paramsLen == 1 && info.Params[0].Type == "string" {
		mapParams.Add(info.Params[0].Field, fParams)
	} else if paramsLen > 0 && fParams != "" {
		splitVals := funcLookupSplit(fParams)
		mapParams = addSplitValsToMapParams(splitVals, info, mapParams)
	}
	if mapParams.Size() > 0 {
		return mapParams
	} else {
		return nil
	}
}

// Used for splitting the values
func addSplitValsToMapParams(splitVals []string, info *Info, mapParams *MapParams) *MapParams {
	for ii := 0; ii < len(splitVals); ii++ {
		if len(info.Params)-1 >= ii {
			if strings.HasPrefix(splitVals[ii], "[") {
				lookupSplits := funcLookupSplit(strings.TrimRight(strings.TrimLeft(splitVals[ii], "["), "]"))
				for _, v := range lookupSplits {
					mapParams.Add(info.Params[ii].Field, v)
				}
			} else {
				mapParams.Add(info.Params[ii].Field, splitVals[ii])
			}
		}
	}
	return mapParams
}