package evaluator

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strconv"

	"github.com/gin-gonic/gin"
	"gitlab.com/olanguage/olang/object"
)

type ServerFunction struct {
	Name   string
	Router *gin.Engine
}

func NewServer(name string) *ServerFunction {
	return &ServerFunction{
		Name: name,
	}
}

func (fn *ServerFunction) Server(args ...object.Object) object.Object {
	gin.SetMode(gin.ReleaseMode)

	if len(args) <= 0 {
		return serverError("wrong number of arguments. got=%d, want=3", len(args))
	}

	port := ""
	response := ""
	server := NewServer(fn.Name)

	switch arg := args[0].(type) {
	case *object.Integer:
		port = string(arg.Value)
	case *object.String:
		port = string(arg.Value)
	default:
		return serverError("argument to `"+fn.Name+"` not supported, got %s", args[1].Type())
	}

	switch arg := args[1].(type) {
	case *object.Route:
		server.RouterGenerator(arg.Config)
		server.Router.Run(":" + port)
	case *object.Hash:
		server.RouterGenerator(arg)
		server.Router.Run(":" + port)
	case *object.Array:
		for _, arg := range arg.Elements {
			response += string(arg.Inspect())
		}
		server.Router.GET("/", func(ctx *gin.Context) {
			ctx.Header("X-APP", "O Language Web Server")

			ctx.String(200, response)
		})
		server.Router.Run(":" + port)
	case *object.String:
		server.Router.GET("/", func(ctx *gin.Context) {
			ctx.Header("X-APP", "O Language Web Server")

			ctx.String(200, arg.Result())
		})
		server.Router.Run(":" + port)
	default:
		return serverError("argument to `"+fn.Name+"` not supported, got %s", args[2].Type())
	}

	return &object.Empty{}
}

func (fn *ServerFunction) RouterGenerator(obj object.Object) *ServerFunction {

	router := gin.New()

	switch group := obj.(type) {
	case *object.Hash:
		for _, groupElement := range group.Pairs {
			path := groupElement.Key.Result()

			var optionType object.Object
			var optionResponse object.Object
			var optionHeaders object.Object
			var optionFileInput object.Object
			var maxUploadSize object.Object
			var folderSection object.Object
			var bannerSection object.Object
			var statusSection object.Object
			//var formSection object.Object

			response := ""

			/*
				TODO: fonksiyon paremetreleri url parametreleri olacak
				url parametreleri env'e set edilip okunacak ve fonksiyondaki yerine yazılacak


			*/

			switch groupElementOptions := groupElement.Value.(type) {
			case *object.Hash:
				for _, groupElementOptions := range groupElementOptions.Pairs {
					optionKey := groupElementOptions.Key.Result()

					switch optionKey {
					case "type":
						optionType = groupElementOptions.Value
					case "response":
						optionResponse = groupElementOptions.Value
						response = optionResponse.Result()
					case "input":
						optionFileInput = groupElementOptions.Value
					case "maxsize":
						maxUploadSize = groupElementOptions.Value
					case "headers":
						optionHeaders = groupElementOptions.Value
					case "folder":
						folderSection = groupElementOptions.Value
					case "banner":
						bannerSection = groupElementOptions.Value
					case "status":
						statusSection = groupElementOptions.Value
						/*
							case "form":
								formSection = groupElementOptions.Value
						*/
					}
				}
				var result = func(ctx *gin.Context) {
					ctx.Header("Content-Type", "text/html; charset=utf-8")
					if bannerSection != nil && bannerSection.Result() == "true" {
						ctx.Header("X-APP", "O Language Web Server")
					}

					switch res := optionResponse.(type) {
					case *object.Function:
						ctx.MultipartForm()
						request := &object.Hash{}
						request.Pairs = make(map[object.HashKey]object.HashPair)

						form := &object.Hash{}
						form.Pairs = make(map[object.HashKey]object.HashPair)
						for key, value := range ctx.Request.PostForm {
							key := &object.String{Value: key}
							newValue := &object.String{Value: value[0]}

							if len(value) > 1 {
								newValue := &object.Array{}
								for _, v := range value {
									newValue.Elements = append(newValue.Elements, &object.String{Value: v})
								}
							} else {
								newValue = &object.String{Value: value[0]}
							}

							form.Pairs[key.HashKey()] = object.HashPair{Key: key, Value: newValue}
						}
						requestKey := &object.String{Value: "request"}
						request.Pairs[requestKey.HashKey()] = object.HashPair{Key: &object.String{Value: "request"}, Value: form}

						header := &object.Hash{}
						header.Pairs = make(map[object.HashKey]object.HashPair)
						for key, value := range ctx.Request.Header {
							key := &object.String{Value: key}
							newValue := &object.String{Value: value[0]}

							if len(value) > 1 {
								newValue := &object.Array{}
								for _, v := range value {
									newValue.Elements = append(newValue.Elements, &object.String{Value: v})
								}
							} else {
								newValue = &object.String{Value: value[0]}
							}

							header.Pairs[key.HashKey()] = object.HashPair{Key: key, Value: newValue}
						}
						headerKey := &object.String{Value: "header"}
						request.Pairs[headerKey.HashKey()] = object.HashPair{Key: &object.String{Value: "header"}, Value: header}

						query := &object.Hash{}
						query.Pairs = make(map[object.HashKey]object.HashPair)
						for key, value := range ctx.Request.URL.Query() {
							key := &object.String{Value: key}
							newValue := &object.String{Value: value[0]}

							if len(value) > 1 {
								newValue := &object.Array{}
								for _, v := range value {
									newValue.Elements = append(newValue.Elements, &object.String{Value: v})
								}
							} else {
								newValue = &object.String{Value: value[0]}
							}

							query.Pairs[key.HashKey()] = object.HashPair{Key: key, Value: newValue}
						}
						queryKey := &object.String{Value: "query"}
						request.Pairs[queryKey.HashKey()] = object.HashPair{Key: &object.String{Value: "query"}, Value: query}

						param := &object.Hash{}
						param.Pairs = make(map[object.HashKey]object.HashPair)
						for _, param := range ctx.Params {
							key := &object.String{Value: param.Key}
							newValue := &object.String{Value: param.Value}

							query.Pairs[key.HashKey()] = object.HashPair{Key: key, Value: newValue}
						}
						paramKey := &object.String{Value: "param"}
						request.Pairs[paramKey.HashKey()] = object.HashPair{Key: &object.String{Value: "param"}, Value: param}

						response = ApplyFunctionInside(res, []object.Object{request}).Result()

					default:
						response = res.Result()
					}

					html := response

					switch optiHeaders := optionHeaders.(type) {
					case *object.Hash:
						for _, headers := range optiHeaders.Pairs {
							ctx.Header(headers.Key.Result(), headers.Value.Result())
						}
					}

					/*

						switch formData := formSection.(type) {
						case *object.Hash:
							for _, data := range formData.Pairs {
								html = strings.Replace(html, "<?data["+data.Key.Result()+"]?>", ctx.PostForm(data.Key.Result()), -1)
							}
						}

					*/

					statusCode := 200

					if statusSection != nil {
						switch stat := statusSection.(type) {
						case *object.Integer:
							statusCode = int(stat.Value)
						default:
							statusCode = 200
						}
					} else {
						statusCode = 200
					}

					ctx.String(statusCode, html)
				}

				upload := func(ctx *gin.Context) {
					ctx.Header("Content-Type", "text/html; charset=utf-8")
					if bannerSection != nil && bannerSection.Result() == "true" {
						ctx.Header("X-APP", "O Language Web Server")
					}
					switch optiHeaders := optionHeaders.(type) {
					case *object.Hash:
						for _, headers := range optiHeaders.Pairs {
							ctx.Header(headers.Key.Result(), headers.Value.Result())
						}
					}

					maxUploadSize, _ := strconv.ParseInt(maxUploadSize.Result(), 10, 32)
					router.MaxMultipartMemory = maxUploadSize << 20 // 20 left shift from maxUploadKbSize

					form, err := ctx.MultipartForm()
					if err != nil {
						ctx.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
						return
					}
					files := form.File[optionFileInput.Result()]

					var fileArr []interface{}

					for _, file := range files {
						filename := filepath.Base(file.Filename)

						if err := ctx.SaveUploadedFile(file, folderSection.Result()+"/"+filename); err != nil {
							ctx.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
							return
						}

						fileArr = append(fileArr, file)
					}

					statusCode := 200

					if statusSection != nil {
						switch stat := statusSection.(type) {
						case *object.Integer:
							statusCode = int(stat.Value)
						default:
							statusCode = 200
						}
					} else {
						statusCode = 200
					}

					ctx.JSON(statusCode, fileArr)
				}

				switch optionType.Result() {
				case "GET":
					router.GET(path, result)
				case "POST":
					router.POST(path, result)
				case "PUT":
					router.PUT(path, result)
				case "DELETE":
					router.DELETE(path, result)
				case "PATCH":
					router.PATCH(path, result)
				case "OPTIONS":
					router.OPTIONS(path, result)
				case "UPLOAD":
					router.PUT(path, upload)
					router.POST(path, upload)
				case "STATIC":
					if folderSection != nil {
						router.Static(path, folderSection.Result())
					} else {
						serverError("folder section not found!")
					}
				}
			default:
				serverError("argument to `"+fn.Name+"` not supported, got %s", groupElementOptions.Type())
			}
		}
	}

	fn.Router = router
	return fn
}

func serverError(format string, a ...interface{}) *object.Error {
	return &object.Error{Message: fmt.Sprintf(format, a...)}
}
