package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
)

// RunTest Executes each individual test case
func RunTest(testCase *TestCase) error {
	var err error
	var requestUrl string
	var requestType string
	var req *http.Request
	//var bodyMap map[string]interface{}

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: testDefinitions.Insecure},
	}
	client := &http.Client{Transport: tr}

	fmt.Printf("\nRunning %s : %s\n\n", testCase.Name, testCase.Description)

	// Determine URL
	if len(testDefinitions.BaseUrl) > 0 {
		//fmt.Printf("Joining '%s' with '%s'\n", testDefinitions.BaseUrl, testCase.Path)

		requestUrl, err = url.JoinPath(testDefinitions.BaseUrl, testCase.Path)
		if err != nil {
			errMessage := fmt.Sprintf("error combining request URL : '%s'\n", err)
			return errors.New(errMessage)
		}

		decoded, err := url.QueryUnescape(requestUrl)
		if err != nil {
			errMessage := fmt.Sprintf("error unescaping request URL : '%s'\n", err)
			return errors.New(errMessage)
		}
		requestUrl = decoded
		//fmt.Printf("url path decoded '%s'\n", decoded)

	} else {
		requestUrl = testCase.Path
	}

	// Populate request body

	// Get request type, default to GET
	if len(testCase.Method) == 0 {
		//return errors.New("no test method specifed, must be either GET or POST")

		// Assume GET if no method specified
		requestType = "GET"
	} else {
		requestType = testCase.Method
	}

	fmt.Printf("Preparing %s for endpoint %s\n", requestType, requestUrl)

	// Create request
	if len(testCase.Body) > 0 {

		// unmarshal testCase.Body so we can search for replacements
		var jsonBody map[string]interface{}
		err = json.Unmarshal(testCase.Body, &jsonBody)
		if err != nil {
			error := fmt.Sprintf("Error processing request body for test case : '%s'\n", err)
			panic(error)
		}

		// iterate through the key-value pairs and check for replacements
		//fmt.Printf("Capture data : '%+v'\n", captureValues.Data)
		BodyReplaceCapture(jsonBody, captureValues.Data)

		// turn the unmarshalled object back into a []byte so that we can send it
		modifiedJsonData, err := json.Marshal(jsonBody)
		if err != nil {
			panic(err)
		}

		// Check for capture tokens that need to be replaced before adding the value
		//newBody := ReplaceCaptures(string(testCase.Body[:]))

		fmt.Printf("Adding body to request : %s\n", modifiedJsonData)
		req, err = http.NewRequest(requestType, requestUrl, bytes.NewBuffer(modifiedJsonData))

		if err != nil {
			errMessage := fmt.Sprintf("error submitting request : '%s'\n", err)
			return errors.New(errMessage)
		}

	} else {
		req, err = http.NewRequest(requestType, requestUrl, nil)
	}

	if err != nil {
		errMessage := fmt.Sprintf("error submitting request : '%s'\n", err)
		return errors.New(errMessage)
	}

	// Assemble headers
	// Global headers first
	if len(testDefinitions.Headers) > 0 {
		//fmt.Printf("Adding global headers to request\n")
		for k, v := range testDefinitions.Headers {
			// Check for capture tokens that need to be replaced before adding the value
			key := HeaderReplaceCaptures(k)
			val := HeaderReplaceCaptures(v)

			//fmt.Printf("Add global header %s = %s\n", key, val)
			req.Header.Add(key, val)
		}
	}

	// Then any test case specific headers
	for k, v := range testCase.Header {
		// Check for capture tokens that need to be replaced before adding the value
		key := HeaderReplaceCaptures(k)
		val := HeaderReplaceCaptures(v)

		//fmt.Printf("Add header %s = %s\n", key, val)
		req.Header.Add(key, val)
	}

	// Perform request
	fmt.Printf("Sending request\n")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return err
	}
	defer resp.Body.Close()

	// Store response
	testCase.ResultStatusCode = resp.StatusCode
	testCase.ResultHeaders = resp.Header

	fmt.Printf("Status Code: %s\n", resp.Status)
	fmt.Printf("Header response:\n%+v\n", testCase.ResultHeaders)

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return err
	}

	testCase.ResultBody = string(body)
	fmt.Printf("Body response:\n%s\n", testCase.ResultBody)

	// We have no idea what the response will look like so use an interface and unmarshal manually as needed
	if len(body) > 0 {
		var b interface{}
		json.Unmarshal(body, &b)
		if b != nil {
			testCase.ResultBodyMap = b.(map[string]interface{})
			//fmt.Printf("Body map: '%v'\n", testCase.ResultBodyMap)
			//bodyMap = b.(map[string]interface{})
		}
	}

	// Iterate through testDefinitions.CaptureCases and see if there are any that match this test
	// If we found one, add the appropriate replacement to captureValues
	for i := range testDefinitions.CaptureCases {
		if testDefinitions.CaptureCases[i].TestCaseName == testCase.Name {
			fmt.Printf("\nProcessing capture(s) for test case '%s'\n", testCase.Name)
			captureData := testDefinitions.CaptureCases[i].CaptureData

			for k, v := range captureData.Body.Data {
				fmt.Printf("Searching body response for match on capture string '%s'\n", k)

				results := findKey(k, testCase.ResultBodyMap)
				//fmt.Printf("Results : '%v'\n", results)

				if len(results) > 0 {
					//fmt.Printf("Found %d results but only storing the first one\n", len(results))

					// Get the type of the first element
					valueType := reflect.TypeOf(results[0])

					// Check if the type is not string
					if valueType.Kind() != reflect.String {
						// Convert the value to string
						results[0] = fmt.Sprintf("%v", results[0])
					}

					fmt.Printf("Storing capture '%s' = '%s'\n", k, results[0].(string))
					captureValues.Data[v] = results[0].(string)
				}
			}

			// Capture anything needed
			break
		}
	}

	// No errors if we reached this point
	return nil
}

// HeaderReplaceCaptures iterates through captureValues to apply any text replacements necessary for input string
func HeaderReplaceCaptures(input string) string {

	for k, v := range captureValues.Data {
		input = strings.Replace(input, k, v, -1)
		/*
			if strings.Contains(input, k) {
				//fmt.Printf("Found key '%s' in input string '%s', replacing with '%s'\n", k, input, v)

			}
		*/
	}

	return input
}

// Search unmarshalled json data for any replacements to make from data already captured by previous tests
func BodyReplaceCapture(data interface{}, captureValues map[string]string) {
	switch v := data.(type) {
	case map[string]interface{}:
		for key, val := range v {
			//fmt.Printf("NewReplaceCapture checking '%v' = '%v'\n", key, val)

			// Check if key needs to be replaced
			if replaceKey, ok := captureValues[key]; ok {
				//fmt.Printf("Replacing key '%s'\n", key)
				delete(v, key)
				v[replaceKey] = val
				key = replaceKey
			}

			// Check if value needs to be replaced
			if strVal, ok := val.(string); ok {
				//fmt.Printf("Checking value '%s'\n", strVal)
				if replaceVal, ok := captureValues[strVal]; ok {

					if strings.HasPrefix(strVal, "int:") {
						fmt.Printf("Replacing capture token '%s' with int version of '%s'\n", strVal, replaceVal)
						i, err := strconv.Atoi(replaceVal)
						if err != nil {
							panic(err)
						}
						v[key] = i
					} else {
						fmt.Printf("Replacing capture token '%s' with '%s'\n", strVal, replaceVal)
						v[key] = replaceVal
					}

				}
			}

			if replaceVal, ok := captureValues[key]; ok {
				//fmt.Printf("Replacing value '%s'\n", captureValues[key])
				v[key] = replaceVal
			}

			BodyReplaceCapture(val, captureValues)
		}
	case []interface{}:
		for i, val := range v {
			fmt.Printf("i val : '%v' = '%v'\n", i, val)
			BodyReplaceCapture(val, captureValues)
			v[i] = val
		}
	}
}
