package main

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/iancoleman/orderedmap"
)

func ReadInput(data []byte, testDefinitions *TestDefinitions) {
	var err error

	// Unmarshal the json into an orderedmap to preserve the ordering of json structure
	o := orderedmap.New()
	err = json.Unmarshal([]byte(data), &o)
	if err != nil {
		error := fmt.Sprintf("JSON Unmarshal error %s\n", err)
		panic(error)
	}

	topLevel := o.Keys()
	//fmt.Printf("Found %d top-level keys in json data\n", len(topLevel))

	// TODO : Check required top level keys are present
	if len(topLevel) <= 1 {
		error := "Missing required keys in input json"
		panic(error)
	}

	// Get the name of the group of tests
	if data, ok := o.Get("name"); ok {
		testDefinitions.Name = data.(string)
		//fmt.Printf("test name: '%s'\n", testDefinitions.Name)
	}

	// Get the base url if defined
	if data, ok := o.Get("url"); ok {
		testDefinitions.BaseUrl = data.(string)
	}

	// Check for any headers to include with all requests
	if data, ok := o.Get("header"); ok {
		headerMap := data.(orderedmap.OrderedMap)
		testDefinitions.Headers = OrderedToStringMap(headerMap)
	}

	// Check if we should ignore certificate errors
	if data, ok := o.Get("insecure"); ok {
		testDefinitions.Insecure = data.(bool)
	}

	// Get a reference to the node containing each of the test cases
	testsInterface, ok := o.Get("testCases")
	if !ok {
		panic("No key defining test cases found\n")
	}

	// Process the "capture" settings that allow us to use values from one test case in subsequent test cases
	// each capture node has a name that links it to the test case that captures that data
	if capture, ok := o.Get("capture"); ok {
		captureMap := capture.(orderedmap.OrderedMap)
		captureKeys := captureMap.Keys()
		for _, outerKey := range captureKeys {
			// Define a new capture case object to record these details
			thisCaptureCase := new(CaptureCase)

			// Make sure we can access the capture defintiion
			if captureCase, ok := captureMap.Get(outerKey); ok {
				// Store the test case name that this capture will come from
				thisCaptureCase.TestCaseName = outerKey
				//fmt.Printf("[%d] : Capture from %s\n", i, outerKey)

				// Get capture data from body
				thisCaptureCaseMap := captureCase.(orderedmap.OrderedMap)
				if body, ok := thisCaptureCaseMap.Get("body"); ok {
					//fmt.Printf("This capture case has body defined\n")

					bodyMap := body.(orderedmap.OrderedMap)
					bodyMapKeys := bodyMap.Keys()

					bodyData := make(map[string]string)

					for _, bodyKey := range bodyMapKeys {
						if bodyVal, ok := bodyMap.Get(bodyKey); ok {
							switch vType := bodyVal.(type) {
							case string:
								//fmt.Printf("Capturing '%s' from test result and using '%s' for replacement token\n", bodyKey, bodyVal.(string))

								// Store capture info
								bodyData[bodyKey] = bodyVal.(string)

							default:
								fmt.Printf("received unexpected value type, %T\n", vType)
							}
						}
					}
					captureBody := new(CaptureBody)
					captureBody.Data = bodyData
					thisCaptureCase.CaptureData.Body = *captureBody

				}
				// TODO : header
			}

			testDefinitions.CaptureCases = append(testDefinitions.CaptureCases, *thisCaptureCase)
		}
	}

	// Get the keys for the first test so we know what config options have been specified
	testCasesMap := testsInterface.(orderedmap.OrderedMap)
	testCasesKeys := testCasesMap.Keys()

	// Parse json into our testCaseDefinition

	// Parse each key into our config struct
	fmt.Printf("Loading %d test cases\n", len(testCasesKeys))
	for _, outerKey := range testCasesKeys {

		//fmt.Printf("Test %d : %s\n", i, outerKey)

		// Get the name of the test case
		thisTestCase := new(TestCase)
		thisTestCase.Name = outerKey

		if testCase, ok := testCasesMap.Get(outerKey); ok {
			// Get the details of the test case
			thisTestCaseMap := testCase.(orderedmap.OrderedMap)

			// Path
			if val, ok := thisTestCaseMap.Get("path"); ok {
				thisTestCase.Path = val.(string)
			}
			// Method
			if val, ok := thisTestCaseMap.Get("method"); ok {
				thisTestCase.Method = val.(string)
			}
			// Description
			if val, ok := thisTestCaseMap.Get("description"); ok {
				thisTestCase.Description = val.(string)
			}
			// Disabled
			if val, ok := thisTestCaseMap.Get("disabled"); ok {
				thisTestCase.Disabled = val.(bool)
			}
			// Body
			if val, ok := thisTestCaseMap.Get("body"); ok {
				// Create a normal string map for all the body key-value pairs

				// TODO : Support nested json instead of single level flat request

				//thisTestCase.Body = OrderedToStringMap(val.(orderedmap.OrderedMap))

				bytes, err := json.Marshal(val)
				if err != nil {
					error := fmt.Sprintf("Error processing request body for test case : '%s'\n", err)
					panic(error)
				} else {
					thisTestCase.Body = bytes
					//fmt.Printf("Body marshalled:\n%v\n", string(bytes))
				}
			}
			// Header
			if val, ok := thisTestCaseMap.Get("header"); ok {
				// Create a normal string map for all the body key-value pairs
				thisTestCase.Header = OrderedToStringMap(val.(orderedmap.OrderedMap))
			}

			// Expect - this is more tricky since it is yet another json fragment
			if val, ok := thisTestCaseMap.Get("expect"); ok {
				expectOptions := new(ExpectOptions)

				// expect can have header and body definitions
				expectMap := val.(orderedmap.OrderedMap)

				// Handle the possible checks for a header
				if expectVal, ok2 := expectMap.Get("header"); ok2 {
					ReadHeaderTestCases(expectVal.(orderedmap.OrderedMap), &expectOptions.Header)
				}

				// Handle the possible checks for a body
				if expectVal, ok2 := expectMap.Get("body"); ok2 {
					ReadBodyTestCases(expectVal.(orderedmap.OrderedMap), &expectOptions.Body)
				}

				thisTestCase.Expect = *expectOptions
			}
		}
		testDefinitions.TestCases = append(testDefinitions.TestCases, *thisTestCase)
	}
}

func ReadHeaderTestCases(input orderedmap.OrderedMap, result *HeaderTests) {
	result.Contains = make(map[string]string)
	result.Equals = make(map[string]string)

	// Contains check
	if val, ok := input.Get("contains"); ok {
		containsMap := val.(orderedmap.OrderedMap)
		result.Contains = OrderedToStringMap(containsMap)
	}

	// Equals check
	if val, ok := input.Get("equals"); ok {
		equalsMap := val.(orderedmap.OrderedMap)
		result.Equals = OrderedToStringMap(equalsMap)
	}
}

func ReadBodyTestCases(input orderedmap.OrderedMap, result *BodyTests) {
	result.Contains = make(map[string]string)
	result.Equals = make(map[string]string)

	// TODO : Use tags in struct rather than hard coding all the different check types
	// using https://stackoverflow.com/a/23840419 as an idea

	// Contains check
	if val, ok := input.Get("contains"); ok {
		// Confirm this is an ordered map
		if _, ok := val.(orderedmap.OrderedMap); !ok {
			error := fmt.Sprintf("Unexpected json definition for Body Contains check. Type is '%v' but expected Object.\n", reflect.TypeOf(val))
			panic(error)
		}

		containsMap := val.(orderedmap.OrderedMap)
		result.Contains = OrderedToStringMap(containsMap)
	}

	// Equals check
	if val, ok := input.Get("equals"); ok {
		equalsMap := val.(orderedmap.OrderedMap)
		result.Equals = OrderedToStringMap(equalsMap)
	}

	// Has Keys check
	if val, ok := input.Get("hasKeys"); ok {
		// This is an array not a map
		hasKeysArray := val.([]interface{})
		result.HasKeys = OrderedToStringSlice(hasKeysArray)
	}

	// TODO : remaining tests
}
