package api_documentation

import (
	"fmt"
	"reflect"
	"strings"

	"gitlab.com/uafrica/go-utils/handler_utils"
)

type NoParams struct{}
type DocPath map[string]DocMethodInfo


type Docs struct {
	Paths map[string]DocPath `json:"paths"`
	Definitions map[string]interface{}
}


type DocMethodInfo struct {
	Summary    string              `json:"summary"`
	Tags []string `json:"tags"`
	Parameters []DocParam `json:"parameters,omitempty"`
	Responses    map[string]DocResponseValue         `json:"responses,omitempty"`
}

type DocParam struct {
	Name        string
	In string
	Type        string
	Description string
	Schema DocSchema
}

type DocSchema struct {
	Ref string `json:"$ref"`
}

type DocResponseValue struct {
	Description string  `json:"description"`
	Schema      *DocSchemaResponse `json:"schema,omitempty"`
}

type DocSchemaResponse struct {
	Type  *string `json:"type,omitempty"`
	Items DocSchema  `json:"items"`
}


func GetDocs(endpointHandlers map[string]map[string]interface{}) (Docs, error) {
	docs := Docs{
		Paths: map[string]DocPath{},
	}

	var validationError error
	if endpointHandlers, validationError = handler_utils.ValidateAPIEndpoints(endpointHandlers); validationError != nil {
		return Docs{}, validationError
	}


	for path, methods := range endpointHandlers {
		docPath := DocPath{}
		for method, methodHandler := range methods {
			docMethod := DocMethodInfo{}
			if handler, ok := methodHandler.(handler_utils.Handler); !ok {
				docMethod.Summary = "Not available"
			} else {
				//purpose
				docMethod.Summary = "Not available - see request and response structs"
				docMethod.Tags = []string{path}

				//describe parameters
				docMethod.Parameters = []DocParam{}
				for i := 0; i < handler.RequestParamsType.NumField(); i++ {
					f := handler.RequestParamsType.Field(i)

					name := f.Tag.Get("json")
					if name == "" {
						name = f.Name
					}

					parameter := DocParam{
						Name:        name,
						Type:        fmt.Sprintf("%v", f.Type),
						Description: f.Tag.Get("doc"),
					}

					docMethod.Parameters = append(docMethod.Parameters, parameter)
				}

				// Request
				 if handler.RequestBodyType != nil {
					 body := reflect.New(handler.RequestBodyType).Interface()
					 bodyTypeString := getType(body)
					 parameter := DocParam{
						 Name:        "body",
						 In: 		"body",
						 Schema: DocSchema{Ref: "#/definitions/"+bodyTypeString},
					 }

					 docMethod.Parameters = append(docMethod.Parameters, parameter)
					 docs.Definitions[bodyTypeString] = ""
				 }

				// Response
				 if handler.ResponseType != nil {
					 responses := map[string]DocResponseValue{}
					 responseBody := reflect.New(handler.RequestBodyType).Interface()
					 responseBodyTypeString := getType(responseBody)

					 responses["200"] = DocResponseValue{
						 Description: "successful operation",
						 Schema: &DocSchemaResponse{
							 Items: DocSchema{Ref:  "#/definitions/"+responseBodyTypeString},
						 },
					 }

					 docMethod.Responses = responses
					 docs.Definitions[responseBodyTypeString] = ""
				 }





				//describe request schema
				// var err error
				// docMethod.Request, err = DocSchema(fmt.Sprintf("%s %s %s", method, path, "request"), handler.RequestBodyType)
				// if err != nil {
				// 	return Docs{}, errors.Wrapf(err, "failed to document request")
				// }
				// docMethod.Response, err = DocSchema(fmt.Sprintf("%s %s %s", method, path, "response"), handler.ResponseType)
				// if err != nil {
				// 	return Docs{}, errors.Wrapf(err, "failed to document response")
				// }
			}

			docPath[strings.ToLower(method)] = docMethod
		}
		docs.Paths[path] = docPath
	}


	return docs, nil
}

func getType(myvar interface{}) string {
	if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr {
		return t.Elem().Name()
	} else {
		return t.Name()
	}
}
//
// func DocSchema(description string, t reflect.Type) (interface{}, error) {
// 	if t == nil {
// 		return nil, nil
// 	}
// 	schema := map[string]interface{}{
// 		"description": description,
// 	}
//
// 	if t.Kind() == reflect.Ptr {
// 		schema["optional"] = true
// 		t = t.Elem()
// 	}
//
// 	switch t.Kind() {
// 	case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int,
// 		reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint,
// 		reflect.Float64, reflect.Float32,
// 		reflect.Bool,
// 		reflect.String:
// 		schema["type"] = fmt.Sprintf("%v", t)
//
// 	case reflect.Interface:
// 		schema["type"] = "interface{}" //any value...?
//
// 	case reflect.Struct:
// 		schema["type"] = "object"
// 		properties := map[string]interface{}{}
// 		for i := 0; i < t.NumField(); i++ {
// 			f := t.Field(i)
// 			if !f.Anonymous {
// 				fieldName := f.Tag.Get("json")
// 				if fieldName == "" {
// 					fieldName = f.Name
// 				}
// 				if fieldName == "-" {
// 					continue //json does not marshal these
// 				}
// 				fieldName = strings.Replace(fieldName, ",omitempty", "", -1)
//
// 				var err error
// 				fieldDesc := f.Tag.Get("doc")
// 				if fieldDesc == "" {
// 					fieldDesc = description + "." + fieldName
// 				}
// 				properties[fieldName], err = DocSchema(fieldDesc, f.Type)
// 				if err != nil {
// 					return nil, errors.Wrapf(err, "failed to document %v.%s", t, fieldName)
// 				}
// 			}
// 		}
// 		schema["properties"] = properties
//
// 	case reflect.Map:
// 		schema["type"] = "map"
// 		// keySchema, err := DocSchema("key", t.Key())
// 		// if err != nil {
// 		// 	return nil, errors.Wrapf(err, "cannot make schema for %v map key", t)
// 		// }
// 		// schema["key"] = keySchema
// 		// // elemSchema, err := DocSchema("items", t.Elem())
// 		// if err != nil {
// 		// 	return nil, errors.Wrapf(err, "cannot make schema for %v map elem", t)
// 		// }
// 		// schema["items"] = elemSchema
//
// 	case reflect.Slice:
// 		schema["type"] = "array"
// 		// elemSchema, err := DocSchema("items", t.Elem())
// 		// if err != nil {
// 		// 	return nil, errors.Wrapf(err, "cannot make schema for %v slice elem", t)
// 		// }
// 		// schema["items"] = elemSchema
//
// 	default:
// 		return nil, errors.Errorf("cannot generate schema for %v kind=%v", t, t.Kind())
// 	}
//
// 	return schema, nil
// }