package gofakeit

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"regexp/syntax"
	"strings"
)

// Generate fake information from given string.
// Replaceable values should be within {}
//
// Functions
// Ex: {firstname} - billy
// Ex: {sentence:3} - Record river mind.
// Ex: {number:1,10} - 4
// Ex: {uuid} - 590c1440-9888-45b0-bd51-a817ee07c3f2
//
// Letters/Numbers
// Ex: ### - 481 - random numbers
// Ex: ??? - fda - random letters
//
// For a complete list of runnable functions use FuncsLookup
func Generate(dataVal string) (string, error) { return generate(GlobalFaker, dataVal) }

// Generate fake information from given string.
// Replaceable values should be within {}
//
// Functions
// Ex: {firstname} - billy
// Ex: {sentence:3} - Record river mind.
// Ex: {number:1,10} - 4
// Ex: {uuid} - 590c1440-9888-45b0-bd51-a817ee07c3f2
//
// Letters/Numbers
// Ex: ### - 481 - random numbers
// Ex: ??? - fda - random letters
//
// For a complete list of runnable functions use FuncsLookup
func (f *Faker) Generate(dataVal string) (string, error) { return generate(f, dataVal) }

func generate(f *Faker, dataVal string) (string, error) {
	// Replace # with numbers and ? with letters
	dataVal = replaceWithNumbers(f, dataVal)
	dataVal = replaceWithLetters(f, dataVal)

	// Check if string has any replaceable values
	if !strings.Contains(dataVal, "{") {
		return dataVal, nil
	}

	var result strings.Builder
	result.Grow(len(dataVal) * 2) // Pre-allocate with estimate

	i := 0
	for i < len(dataVal) {
		// Find next opening brace
		start := strings.IndexByte(dataVal[i:], '{')
		if start == -1 {
			// No more replacements, append rest and break
			result.WriteString(dataVal[i:])
			break
		}
		start += i

		// Append everything before the brace
		result.WriteString(dataVal[i:start])

		// Find matching closing brace (handle nested brackets)
		end := -1
		depth := 0
		for j := start; j < len(dataVal); j++ {
			if dataVal[j] == '{' {
				depth++
			} else if dataVal[j] == '}' {
				depth--
				if depth == 0 {
					end = j
					break
				}
			}
		}

		if end == -1 {
			// No closing brace, append rest and break
			result.WriteString(dataVal[start:])
			break
		}

		// Extract function name and params
		fParts := dataVal[start+1 : end]
		fName, fParams, _ := strings.Cut(fParts, ":")

		// Check if it's a replaceable lookup function
		if info := GetFuncLookup(fName); info != nil {
			// Get parameters
			var mapParams *MapParams
			paramsLen := len(info.Params)

			if paramsLen > 0 && fParams != "" {
				mapParams = NewMapParams()
				// 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 {
					splitVals, err := funcLookupSplit(fParams)
					if err != nil {
						return "", err
					}
					mapParams, err = addSplitValsToMapParams(splitVals, info, mapParams)
					if err != nil {
						return "", err
					}
				}
				if mapParams.Size() == 0 {
					mapParams = nil
				}
			}

			// Call function
			fValue, err := info.Generate(f, mapParams, info)
			if err != nil {
				return "", err
			}

			// Write the generated value
			result.WriteString(fmt.Sprintf("%v", fValue))
			i = end + 1
		} else {
			// Not a valid function, keep the braces
			result.WriteString(dataVal[start : end+1])
			i = end + 1
		}
	}

	return result.String(), nil
}

// FixedWidthOptions defines values needed for csv generation
type FixedWidthOptions struct {
	RowCount int     `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
	Fields   []Field `json:"fields" xml:"fields" fake:"{fields}"`
}

// FixedWidth generates an table of random data in fixed width format
// A nil FixedWidthOptions returns a randomly structured FixedWidth.
func FixedWidth(co *FixedWidthOptions) (string, error) { return fixeWidthFunc(GlobalFaker, co) }

// FixedWidth generates an table of random data in fixed width format
// A nil FixedWidthOptions returns a randomly structured FixedWidth.
func (f *Faker) FixedWidth(co *FixedWidthOptions) (string, error) { return fixeWidthFunc(f, co) }

// Function to generate a fixed width document
func fixeWidthFunc(f *Faker, co *FixedWidthOptions) (string, error) {
	// If we didn't get FixedWidthOptions, create a new random one
	if co == nil {
		co = &FixedWidthOptions{}
	}

	// Make sure you set a row count
	if co.RowCount <= 0 {
		co.RowCount = f.IntN(10) + 1
	}

	// Check fields
	if len(co.Fields) <= 0 {
		// Create random fields
		co.Fields = []Field{
			{Name: "Name", Function: "{firstname} {lastname}"},
			{Name: "Email", Function: "email"},
			{Name: "Password", Function: "password", Params: MapParams{"special": {"false"}, "space": {"false"}}},
		}
	}

	data := [][]string{}
	hasHeader := false

	// Loop through fields, generate data and add to data array
	for _, field := range co.Fields {
		// Start new row
		row := []string{}

		// Add name to first value
		if field.Name != "" {
			hasHeader = true
		}
		row = append(row, field.Name)

		// Get function
		funcInfo := GetFuncLookup(field.Function)
		var value any
		if funcInfo == nil {
			// Try to run the function through generate
			for i := 0; i < co.RowCount; i++ {
				genStr, err := generate(f, field.Function)
				if err != nil {
					return "", err
				}

				row = append(row, genStr)
			}
		} else {
			// Generate function value
			var err error
			for i := 0; i < co.RowCount; i++ {
				value, err = funcInfo.Generate(f, &field.Params, funcInfo)
				if err != nil {
					return "", err
				}

				// Add value to row
				row = append(row, anyToString(value))
			}
		}

		// Add row to data
		data = append(data, row)
	}

	var result strings.Builder

	// Calculate column widths
	colWidths := make([]int, len(data))
	for i, row := range data {
		for _, value := range row {
			width := len(value) + 5
			if width > colWidths[i] {
				colWidths[i] = width
			}
		}
	}

	// Append table rows to the string, excluding the entire row if the first value is empty
	for i := 0; i < len(data[0]); i++ {
		if !hasHeader && i == 0 {
			continue // Skip the entire column if the first value is empty
		}

		var resultRow strings.Builder
		for j, row := range data {
			resultRow.WriteString(fmt.Sprintf("%-*s", colWidths[j], row[i]))
		}

		// Trim trailing spaces
		result.WriteString(strings.TrimRight(resultRow.String(), " "))

		// Only add new line if not the last row
		if i != len(data[0])-1 {
			result.WriteString("\n")
		}
	}

	return result.String(), nil
}

// Regex will generate a string based upon a RE2 syntax
func Regex(regexStr string) string { return regex(GlobalFaker, regexStr) }

// Regex will generate a string based upon a RE2 syntax
func (f *Faker) Regex(regexStr string) string { return regex(f, regexStr) }

func regex(f *Faker, regexStr string) (gen string) {
	re, err := syntax.Parse(regexStr, syntax.Perl)
	if err != nil {
		return "Could not parse regex string"
	}

	// Panic catch
	defer func() {
		if r := recover(); r != nil {
			gen = fmt.Sprint(f)
			return

		}
	}()

	return regexGenerate(f, re, len(regexStr)*100)
}

func regexGenerate(f *Faker, re *syntax.Regexp, limit int) string {
	if limit <= 0 {
		panic("Length limit reached when generating output")
	}

	op := re.Op
	switch op {
	case syntax.OpNoMatch: // matches no strings
		// Do Nothing
	case syntax.OpEmptyMatch: // matches empty string
		return ""
	case syntax.OpLiteral: // matches Runes sequence
		var b strings.Builder
		for _, ru := range re.Rune {
			b.WriteRune(ru)
		}
		return b.String()
	case syntax.OpCharClass: // matches Runes interpreted as range pair list
		// number of possible chars
		sum := 0
		for i := 0; i < len(re.Rune); i += 2 {
			sum += int(re.Rune[i+1]-re.Rune[i]) + 1
			if re.Rune[i+1] == 0x10ffff { // rune range end
				sum = -1
				break
			}
		}

		// pick random char in range (inverse match group)
		if sum == -1 {
			chars := []uint8{}
			for j := 0; j < len(allStr); j++ {
				c := allStr[j]

				// Check c in range
				for i := 0; i < len(re.Rune); i += 2 {
					if rune(c) >= re.Rune[i] && rune(c) <= re.Rune[i+1] {
						chars = append(chars, c)
						break
					}
				}
			}
			if len(chars) > 0 {
				return string([]byte{chars[f.IntN(len(chars))]})
			}
		}

		r := f.IntN(int(sum))
		var ru rune
		sum = 0
		for i := 0; i < len(re.Rune); i += 2 {
			gap := int(re.Rune[i+1]-re.Rune[i]) + 1
			if sum+gap > r {
				ru = re.Rune[i] + rune(r-sum)
				break
			}
			sum += gap
		}

		return string(ru)
	case syntax.OpAnyCharNotNL, syntax.OpAnyChar: // matches any character(and except newline)
		return randCharacter(f, allStr)
	case syntax.OpBeginLine: // matches empty string at beginning of line
	case syntax.OpEndLine: // matches empty string at end of line
	case syntax.OpBeginText: // matches empty string at beginning of text
	case syntax.OpEndText: // matches empty string at end of text
	case syntax.OpWordBoundary: // matches word boundary `\b`
	case syntax.OpNoWordBoundary: // matches word non-boundary `\B`
	case syntax.OpCapture: // capturing subexpression with index Cap, optional name Name
		return regexGenerate(f, re.Sub0[0], limit)
	case syntax.OpStar: // matches Sub[0] zero or more times
		var b strings.Builder
		for i := 0; i < number(f, 0, 10); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(f, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpPlus: // matches Sub[0] one or more times
		var b strings.Builder
		for i := 0; i < number(f, 1, 10); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(f, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpQuest: // matches Sub[0] zero or one times
		var b strings.Builder
		for i := 0; i < number(f, 0, 1); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(f, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpRepeat: // matches Sub[0] at least Min times, at most Max (Max == -1 is no limit)
		var b strings.Builder
		count := 0
		re.Max = int(math.Min(float64(re.Max), float64(10)))
		if re.Max > re.Min {
			count = f.IntN(re.Max - re.Min + 1)
		}
		for i := 0; i < re.Min || i < (re.Min+count); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(f, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpConcat: // matches concatenation of Subs
		var b strings.Builder
		for _, rs := range re.Sub {
			b.WriteString(regexGenerate(f, rs, limit-b.Len()))
		}
		return b.String()
	case syntax.OpAlternate: // matches alternation of Subs
		return regexGenerate(f, re.Sub[number(f, 0, len(re.Sub)-1)], limit)
	}

	return ""
}

// Map will generate a random set of map data
func Map() map[string]any { return mapFunc(GlobalFaker) }

// Map will generate a random set of map data
func (f *Faker) Map() map[string]any { return mapFunc(f) }

func mapFunc(f *Faker) map[string]any {
	m := map[string]any{}

	randWordType := func() string {
		s := randomString(f, []string{"lorem", "bs", "job", "name", "address"})
		switch s {
		case "bs":
			return bs(f)
		case "job":
			return jobTitle(f)
		case "name":
			return name(f)
		case "address":
			return street(f) + ", " + city(f) + ", " + state(f) + " " + zip(f)
		}
		return word(f)
	}

	randSlice := func() []string {
		var sl []string
		for ii := 0; ii < number(f, 3, 10); ii++ {
			sl = append(sl, word(f))
		}
		return sl
	}

	for i := 0; i < number(f, 3, 10); i++ {
		t := randomString(f, []string{"string", "int", "float", "slice", "map"})
		switch t {
		case "string":
			m[word(f)] = randWordType()
		case "int":
			m[word(f)] = number(f, 1, 10000000)
		case "float":
			m[word(f)] = float32Range(f, 1, 1000000)
		case "slice":
			m[word(f)] = randSlice()
		case "map":
			mm := map[string]any{}
			tt := randomString(f, []string{"string", "int", "float", "slice"})
			switch tt {
			case "string":
				mm[word(f)] = randWordType()
			case "int":
				mm[word(f)] = number(f, 1, 10000000)
			case "float":
				mm[word(f)] = float32Range(f, 1, 1000000)
			case "slice":
				mm[word(f)] = randSlice()
			}
			m[word(f)] = mm
		}
	}

	return m
}

func addGenerateLookup() {
	AddFuncLookup("generate", Info{
		Display:     "Generate",
		Category:    "generate",
		Description: "Random string generated from string value based upon available data sets",
		Example:     "{firstname} {lastname} {email} - Markus Moen markusmoen@pagac.net",
		Output:      "string",
		Aliases: []string{
			"template expander",
			"placeholder interpolator",
			"variable substitution",
			"token formatter",
			"pattern builder",
			"macro resolver",
		},
		Keywords: []string{
			"upon", "datasets", "random",
			"string", "value", "available", "data",
			"sets", "based",
		},
		Params: []Param{
			{Field: "str", Display: "String", Type: "string", Description: "String value to generate from"},
		},
		Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
			str, err := info.GetString(m, "str")
			if err != nil {
				return nil, err
			}

			// Limit the length of the string passed
			if len(str) > 1000 {
				return nil, errors.New("string length is too large. limit to 1000 characters")
			}

			return generate(f, str)
		},
	})

	AddFuncLookup("fixed_width", Info{
		Display:     "Fixed Width",
		Category:    "generate",
		Description: "Fixed width rows of output data based on input fields",
		Example: `Name               Email                          Password         Age
Markus Moen        sylvanmraz@murphy.net          6VlvH6qqXc7g     13
Alayna Wuckert     santinostanton@carroll.biz     g7sLrS0gEwLO     46
Lura Lockman       zacherykuhic@feil.name         S8gV7Z64KlHG     12`,
		Output:      "[]byte",
		ContentType: "text/plain",
		Aliases: []string{
			"fixed rows", "columnar data", "padded text", "aligned output", "structured fields",
		},
		Keywords: []string{
			"tabular", "data", "format", "alignment", "columns", "rows", "layout", "monospace", "table", "presentation",
		},
		Params: []Param{
			{Field: "rowcount", Display: "Row Count", Type: "int", Default: "10", Description: "Number of rows"},
			{Field: "fields", Display: "Fields", Type: "[]Field", Description: "Fields name, function and params"},
		},
		Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
			co := FixedWidthOptions{}

			rowCount, err := info.GetInt(m, "rowcount")
			if err != nil {
				return nil, err
			}

			co.RowCount = rowCount

			fields, _ := info.GetStringArray(m, "fields")

			// Check to make sure fields has length
			if len(fields) > 0 {
				co.Fields = make([]Field, len(fields))
				for i, f := range fields {
					// Unmarshal fields string into fields array
					err = json.Unmarshal([]byte(f), &co.Fields[i])
					if err != nil {
						return nil, err
					}
				}
			} else {
				return nil, errors.New("missing fields")
			}

			out, err := fixeWidthFunc(f, &co)
			if err != nil {
				return nil, err
			}

			return out, nil
		},
	})

	AddFuncLookup("regex", Info{
		Display:     "Regex",
		Category:    "generate",
		Description: "Pattern-matching tool used in text processing to search and manipulate strings",
		Example:     "[abcdef]{5} - affec",
		Output:      "string",
		Aliases: []string{
			"regular expression",
			"string matcher",
			"text parser",
			"pattern engine",
			"token analyzer",
			"rule evaluator",
		},
		Keywords: []string{
			"strings", "re2", "syntax",
			"pattern-matching", "tool", "search",
			"validation", "compile", "replace",
		},
		Params: []Param{
			{Field: "str", Display: "String", Type: "string", Description: "Regex RE2 syntax string"},
		},
		Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
			str, err := info.GetString(m, "str")
			if err != nil {
				return nil, err
			}

			// Limit the length of the string passed
			if len(str) > 500 {
				return nil, errors.New("string length is too large. limit to 500 characters")
			}

			return regex(f, str), nil
		},
	})

	AddFuncLookup("map", Info{
		Display:     "Map",
		Category:    "generate",
		Description: "Data structure that stores key-value pairs",
		Example: `{
	"software": 7518355,
	"that": ["despite", "pack", "whereas", "recently", "there", "anyone", "time", "read"],
	"use": 683598,
	"whom": "innovate",
	"yourselves": 1987784
}`,
		Output:      "map[string]any",
		ContentType: "application/json",
		Aliases: []string{
			"associative array",
			"lookup table",
			"symbol table",
			"keyed collection",
			"map structure",
			"object store",
		},
		Keywords: []string{
			"stores", "key", "value",
			"dictionary", "hash", "collection",
			"pairs", "keys", "values", "structure",
		},
		Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
			return mapFunc(f), nil
		},
	})
}
