package functions_test

import (
	. "github.com/mudler/LocalAI/pkg/functions"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("LocalAI function parse tests", func() {
	var functionConfig FunctionsConfig

	BeforeEach(func() {
		// Default configuration setup
		functionConfig = FunctionsConfig{}
	})

	Context("when using grammars and single result expected", func() {
		It("should parse the function name and arguments correctly", func() {
			input := `{"name": "add", "arguments": {"x": 5, "y": 3}}`

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})
	})

	Context("when not using grammars and regex is needed", func() {
		It("should extract function name and arguments from the regex", func() {
			input := `add({"x":5,"y":3})`
			functionConfig.ResponseRegex = []string{`(?P<name>\w+)\s*\((?P<arguments>.*)\)`}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})
		It("should extract function name and arguments from the regex", func() {
			input := `add({"x":5,"y":3})`
			functionConfig.ResponseRegex = []string{`(?P<function>\w+)\s*\((?P<arguments>.*)\)`}
			functionConfig.FunctionNameKey = "function"
			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})
	})

	Context("when having invalid input", func() {
		It("returns no results when there is no input", func() {
			input := ""
			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(0))
		})
		It("returns no results when is invalid", func() {
			input := "invalid input"

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(0))
		})
	})

	Context("when parallel calls are enabled", func() {
		It("should handle multiple function calls", func() {
			input := `[{"name": "add", "arguments": {"x": 5, "y": 3}}, {"name": "subtract", "arguments": {"x": 10, "y": 7}}]`

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(2))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
			Expect(results[1].Name).To(Equal("subtract"))
			Expect(results[1].Arguments).To(Equal(`{"x":10,"y":7}`))
		})
	})

	Context("without grammars and without regex", func() {
		It("should parse the function name and arguments correctly with the name key", func() {
			input := `{"function": "add", "arguments": {"x": 5, "y": 3}}`
			functionConfig.FunctionNameKey = "function"

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})

		It("should parse the function name and arguments correctly with the function key", func() {
			input := `{"name": "add", "arguments": {"x": 5, "y": 3}}`

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})

		It("should parse the result by matching the JSONRegexMatch", func() {
			input := `
<tool_call>
{"name": "add", "arguments": {"x": 5, "y": 3}}
</tool_call>`

			functionConfig.JSONRegexMatch = []string{`(?s)<tool_call>(.*?)</tool_call>`}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})

		It("should parse the result by matching the JSONRegexMatch", func() {
			input := `
{"name": "add", "arguments": {"x": 5, "y": 3}}
</tool_call>`

			functionConfig.JSONRegexMatch = []string{`(?s)(.*?)</tool_call>`}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})

		It("should parse the result even with invalid JSON", func() {
			input := `{"name": "add", "arguments": {"x": 5, "y": 3}} invalid {"name": "add", "arguments": {"x": 5, "y": 3}}`
			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(2))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})
	})

	Context("when using ReplaceResults to clean up input", func() {
		It("should replace text before and after JSON blob", func() {
			input := `
Some text before the JSON
{"name": "add", "arguments": {"x": 5, "y": 3}}
Some text after the JSON
`

			functionConfig.ReplaceFunctionResults = []ReplaceResult{
				{Key: `(?s)^[^{\[]*`, Value: ""},
				{Key: `(?s)[^}\]]*$`, Value: ""},
			}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
		})

		It("should replace text before and after array JSON blob", func() {
			input := `
Some text before the JSON
[{"name": "add", "arguments": {"x": 5, "y": 3}}, {"name": "subtract", "arguments": {"x": 10, "y": 7}}]
Some text after the JSON
`
			functionConfig.ReplaceFunctionResults = []ReplaceResult{
				{Key: `(?s)^[^{\[]*`, Value: ""},
				{Key: `(?s)[^}\]]*$`, Value: ""},
			}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(2))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
			Expect(results[1].Name).To(Equal("subtract"))
			Expect(results[1].Arguments).To(Equal(`{"x":10,"y":7}`))
		})

		It("should convert single-quoted key-value pairs to double-quoted and escape double quotes within values", func() {
			input := `
Some text before the JSON
{'name': '"add"', 'arguments': {'x': 5, 'z': '"v"', 'y': 'v"value"'}}
Some text after the JSON
`
			functionConfig.JSONRegexMatch = []string{`(?s)<tool_call>(.*?)</tool_call>`}

			// Regex to match non-JSON characters before the JSON structure
			//reBefore := regexp.MustCompile(`(?s)^.*?(?=\{|\[)`)
			// Regex to match non-JSON characters after the JSON structure
			//reAfter := regexp.MustCompile(`(?s)(?<=\}|\]).*$`)

			functionConfig.ReplaceFunctionResults = []ReplaceResult{
				{Key: `(?s)^[^{\[]*`, Value: ""},
				{Key: `(?s)[^}\]]*$`, Value: ""},
				// Regex pattern to match single quotes around keys and values
				// Step 1: Replace single quotes around keys and values with double quotes
				{Key: `'([^']*?)'`, Value: `_DQUOTE_${1}_DQUOTE_`},
				// Step 2: Replace double quotes inside values with placeholders
				{Key: `\\"`, Value: `__TEMP_QUOTE__`},
				{Key: `"`, Value: `\"`},
				{Key: `\'`, Value: `'`},
				{Key: `_DQUOTE_`, Value: `"`},
				{Key: `__TEMP_QUOTE__`, Value: `"`},
			}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("\"add\""))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":"v\"value\"","z":"\"v\""}`))
		})

		It("should convert single-quoted key-value pairs to double-quoted and escape double quotes within values", func() {
			input := `
Some text before the JSON
<tool_call>{'name': '"add"', 'arguments': {'x': 5, 'z': '"v"', 'y': 'v"value"'}}</tool_call>
Some text after the JSON
`
			functionConfig.JSONRegexMatch = []string{`(?s)<tool_call>(.*?)</tool_call>`}

			// Regex to match non-JSON characters before the JSON structure
			//reBefore := regexp.MustCompile(`(?s)^.*?(?=\{|\[)`)
			// Regex to match non-JSON characters after the JSON structure
			//reAfter := regexp.MustCompile(`(?s)(?<=\}|\]).*$`)

			functionConfig.ReplaceFunctionResults = []ReplaceResult{
				{Key: `(?s)^[^{\[]*`, Value: ""},
				{Key: `(?s)[^}\]]*$`, Value: ""},
				// Regex pattern to match single quotes around keys and values
				// Step 1: Replace single quotes around keys and values with double quotes
				{Key: `'([^']*?)'`, Value: `_DQUOTE_${1}_DQUOTE_`},
				// Step 2: Replace double quotes inside values with placeholders
				{Key: `\\"`, Value: `__TEMP_QUOTE__`},
				{Key: `"`, Value: `\"`},
				{Key: `\'`, Value: `'`},
				{Key: `_DQUOTE_`, Value: `"`},
				{Key: `__TEMP_QUOTE__`, Value: `"`},
			}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(1))
			Expect(results[0].Name).To(Equal("\"add\""))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":"v\"value\"","z":"\"v\""}`))
		})

		It("should detect multiple functions call where the JSONRegexMatch is repeated", func() {
			input := `
Some text before the JSON
<tool_call>{"name": "add", "arguments": {"x": 5, "y": 3}}</tool_call>
<tool_call>{"name": "subtract", "arguments": {"x": 10, "y": 7}}</tool_call>
Some text after the JSON
`
			functionConfig.JSONRegexMatch = []string{`(?s)<tool_call>(.*?)</tool_call>`}

			results := ParseFunctionCall(input, functionConfig)
			Expect(results).To(HaveLen(2))
			Expect(results[0].Name).To(Equal("add"))
			Expect(results[0].Arguments).To(Equal(`{"x":5,"y":3}`))
			Expect(results[1].Name).To(Equal("subtract"))
			Expect(results[1].Arguments).To(Equal(`{"x":10,"y":7}`))
		})
	})
	Context("ParseTextContent", func() {
		It("Can extract notes from the LLM result", func() {
			input := `
		Some text before the JSON
<sketchpad>
roses are red
</sketchpad>
		<tool_call>{"name": "subtract", "arguments": {"x": 10, "y": 7}}</tool_call>
		Some text after the JSON
		`
			functionConfig.CaptureLLMResult = []string{`(?s)<sketchpad>(.*?)</sketchpad>`}
			results := ParseTextContent(input, functionConfig)
			Expect(results).To(Equal("roses are red"))
		})

		It("Defaults to empty if doesn't catch any", func() {
			input := `
		Some text before the JSON
		<tool_call>{"name": "subtract", "arguments": {"x": 10, "y": 7}}</tool_call>
		Some text after the JSON
		`
			functionConfig.CaptureLLMResult = []string{`(?s)<sketchpad>(.*?)</sketchpad>`}
			results := ParseTextContent(input, functionConfig)
			Expect(results).To(Equal(""))
		})
	})
	Context("ParseJSON - when given valid JSON strings", func() {
		It("should parse multiple JSON objects", func() {
			input := `{"key1": "value1"} {"key2": "value2"}`
			expected := []map[string]any{
				{"key1": "value1"},
				{"key2": "value2"},
			}
			result, err := ParseJSON(input)
			Expect(err).NotTo(HaveOccurred())
			Expect(result).To(Equal(expected))
		})

		It("should parse a single JSON object with various types", func() {
			input := `{"key1": "value1", "key2": 2}`
			expected := []map[string]any{
				{"key1": "value1", "key2": float64(2)},
			}
			result, err := ParseJSON(input)
			Expect(err).NotTo(HaveOccurred())
			Expect(result).To(Equal(expected))
		})
		It("should handle JSON without syntax errors gracefully", func() {
			input := `{"key1": "value1"}`
			expected := []map[string]any{
				{"key1": "value1"},
			}
			result, err := ParseJSON(input)
			Expect(err).NotTo(HaveOccurred())
			Expect(result).To(Equal(expected))
		})
		It("should handle JSON without syntax errors gracefully", func() {
			input := `[{"key1": "value1"}]`
			expected := []map[string]any{
				{"key1": "value1"},
			}
			result, err := ParseJSON(input)
			Expect(err).NotTo(HaveOccurred())
			Expect(result).To(Equal(expected))
		})
	})

	Context("ParseJSON - when given invalid JSON strings", func() {
		It("should return an error for completely invalid JSON", func() {
			input := `invalid json`
			result, err := ParseJSON(input)
			Expect(err).To(HaveOccurred())
			Expect(result).To(BeNil())
		})

		It("should skip invalid JSON parts and parse valid parts", func() {
			input := `{"key1": "value1"} invalid {"key2": "value2"}`
			expected := []map[string]any{
				{"key1": "value1"},
				{"key2": "value2"},
			}
			result, err := ParseJSON(input)
			Expect(err).NotTo(HaveOccurred())
			Expect(result).To(Equal(expected))
		})

		PIt("should handle JSON with syntax errors gracefully", func() {
			input := `{"key1": "value1", "key2": }`
			expected := []map[string]any{
				{"key1": "value1"},
			}
			result, err := ParseJSON(input)
			Expect(err).NotTo(HaveOccurred())
			Expect(result).To(Equal(expected))
		})
	})
})