package api

import (
	"context"
	"docker4ssh/docker"
	"docker4ssh/ssh"
	"encoding/json"
	"fmt"
	"go.uber.org/zap"
	"net/http"
	"reflect"
	"strings"
)

type configGetResponse struct {
	NetworkMode        docker.NetworkMode `json:"network_mode"`
	Configurable       bool               `json:"configurable"`
	RunLevel           docker.RunLevel    `json:"run_level"`
	StartupInformation bool               `json:"startup_information"`
	ExitAfter          string             `json:"exit_after"`
	KeepOnExit         bool               `json:"keep_on_exit"`
}

func ConfigGet(w http.ResponseWriter, r *http.Request, user *ssh.User) (interface{}, int) {
	config := user.Container.Config()

	return configGetResponse{
		config.NetworkMode,
		config.Configurable,
		config.RunLevel,
		config.StartupInformation,
		config.ExitAfter,
		config.KeepOnExit,
	}, http.StatusOK
}

type configPostRequest configGetResponse

var configPostRequestLookup, _ = structJsonLookup(configPostRequest{})

type configPostResponse struct {
	Message  string                       `json:"message"`
	Rejected []configPostResponseRejected `json:"rejected"`
}

type configPostResponseRejected struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

func ConfigPost(w http.ResponseWriter, r *http.Request, user *ssh.User) (interface{}, int) {
	var requestBody map[string]interface{}
	json.NewDecoder(r.Body).Decode(&requestBody)
	defer r.Body.Close()

	var change bool
	var response configPostResponse

	updatedConfig := user.Container.Config()

	for k, v := range requestBody {
		if v == nil {
			continue
		}

		kind, ok := configPostRequestLookup[k]
		if !ok {
			response.Rejected = append(response.Rejected, configPostResponseRejected{
				Name:        k,
				Description: fmt.Sprintf("name / field %s does not exist", k),
			})
		} else {
			valueKind := reflect.TypeOf(v).Kind()
			if valueKind != kind && valueKind == reflect.Float64 && kind == reflect.Int {
				valueKind = reflect.Int
			}

			if valueKind != kind {
				response.Rejected = append(response.Rejected, configPostResponseRejected{
					Name:        k,
					Description: fmt.Sprintf("value should be type %s, got type %s", kind, valueKind),
				})
			}

			change = true
			switch k {
			case "network_mode":
				updatedConfig.NetworkMode = docker.NetworkMode(v.(float64))
			case "configurable":
				updatedConfig.Configurable = v.(bool)
			case "run_level":
				updatedConfig.RunLevel = docker.RunLevel(v.(float64))
			case "startup_information":
				updatedConfig.StartupInformation = v.(bool)
			case "exit_after":
				updatedConfig.ExitAfter = v.(string)
			case "keep_on_exit":
				updatedConfig.KeepOnExit = v.(bool)
			}
		}
	}

	if len(response.Rejected) > 0 {
		var arr []string
		for _, rejected := range response.Rejected {
			arr = append(arr, rejected.Name)
		}

		if len(response.Rejected) == 1 {
			response.Message = fmt.Sprintf("1 invalid configuration was found: %s", strings.Join(arr, ", "))
			return response, http.StatusNotAcceptable
		} else if len(response.Rejected) > 1 {
			response.Message = fmt.Sprintf("%d invalid configurations were found: %s", len(response.Rejected), strings.Join(arr, ", "))
			return response, http.StatusNotAcceptable
		}
	} else if change {
		if err := user.Container.UpdateConfig(context.Background(), updatedConfig); err != nil {
			zap.S().Errorf("Error while updating config for API user %s: %v", user.ID, err)
			response.Message = "Internal error while updating the config"
			return response, http.StatusInternalServerError
		}
	}
	return nil, http.StatusOK
}