package handlers

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/openfaas/faas/gateway/metrics"
	"github.com/openfaas/faas/gateway/requests"
)

// MakeUpdateFunctionHandler request to update an existing function with new configuration such as image, envvars etc.
func MakeUpdateFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client, maxRestarts uint64, restartDelay time.Duration) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := context.Background()

		defer r.Body.Close()
		body, _ := ioutil.ReadAll(r.Body)

		request := requests.CreateFunctionRequest{}
		err := json.Unmarshal(body, &request)
		if err != nil {
			log.Println("Error parsing request:", err)
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(err.Error()))
			return
		}

		serviceInspectopts := types.ServiceInspectOptions{
			InsertDefaults: true,
		}

		service, _, err := c.ServiceInspectWithRaw(ctx, request.Service, serviceInspectopts)
		if err != nil {
			log.Println("Error inspecting service", err)
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(err.Error()))
			return
		}

		secrets, err := makeSecretsArray(c, request.Secrets)
		if err != nil {
			log.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Deployment error: " + err.Error()))
			return
		}
		updateSpec(&request, &service.Spec, maxRestarts, restartDelay, secrets)

		updateOpts := types.ServiceUpdateOptions{}
		updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec

		if len(request.RegistryAuth) > 0 {
			auth, err := BuildEncodedAuthConfig(request.RegistryAuth, request.Image)
			if err != nil {
				log.Println("Error building registry auth configuration:", err)
				w.WriteHeader(http.StatusBadRequest)
				w.Write([]byte("Invalid registry auth"))
				return
			}
			updateOpts.EncodedRegistryAuth = auth
		}

		response, err := c.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts)
		if err != nil {
			log.Println("Error updating service:", err)
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Update error: " + err.Error()))
			return
		}
		log.Println(response.Warnings)
	}
}

func updateSpec(request *requests.CreateFunctionRequest, spec *swarm.ServiceSpec, maxRestarts uint64, restartDelay time.Duration, secrets []*swarm.SecretReference) {

	constraints := []string{}
	if request.Constraints != nil && len(request.Constraints) > 0 {
		constraints = request.Constraints
	} else {
		constraints = linuxOnlyConstraints
	}

	spec.TaskTemplate.RestartPolicy.MaxAttempts = &maxRestarts
	spec.TaskTemplate.RestartPolicy.Condition = swarm.RestartPolicyConditionAny
	spec.TaskTemplate.RestartPolicy.Delay = &restartDelay
	spec.TaskTemplate.ContainerSpec.Image = request.Image
	spec.TaskTemplate.ContainerSpec.Labels = map[string]string{
		"function":              "true",
		"com.openfaas.function": request.Service,
		"com.openfaas.uid":      fmt.Sprintf("%d", time.Now().Nanosecond()),
	}

	if request.Labels != nil {
		for k, v := range *request.Labels {
			spec.TaskTemplate.ContainerSpec.Labels[k] = v
			spec.Annotations.Labels[k] = v
		}
	}

	spec.TaskTemplate.Networks = []swarm.NetworkAttachmentConfig{
		{
			Target: request.Network,
		},
	}
	spec.TaskTemplate.ContainerSpec.Secrets = secrets

	spec.TaskTemplate.Resources = buildResources(request)

	spec.TaskTemplate.Placement = &swarm.Placement{
		Constraints: constraints,
	}

	spec.Annotations.Name = request.Service

	spec.RollbackConfig = &swarm.UpdateConfig{
		FailureAction: "pause",
	}

	spec.UpdateConfig = &swarm.UpdateConfig{
		Parallelism:   1,
		FailureAction: "rollback",
	}

	env := buildEnv(request.EnvProcess, request.EnvVars)

	if len(env) > 0 {
		spec.TaskTemplate.ContainerSpec.Env = env
	}

	if spec.Mode.Replicated != nil {
		spec.Mode.Replicated.Replicas = getMinReplicas(request)
	}
}
