//go:build !remote

package compat

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/containers/podman/v5/libpod"
	"github.com/containers/podman/v5/libpod/define"
	"github.com/containers/podman/v5/pkg/api/handlers"
	"github.com/containers/podman/v5/pkg/api/handlers/utils"
	api "github.com/containers/podman/v5/pkg/api/types"
	"github.com/containers/podman/v5/pkg/domain/entities"
	"github.com/containers/podman/v5/pkg/domain/filters"
	"github.com/containers/podman/v5/pkg/domain/infra/abi"
	"github.com/containers/podman/v5/pkg/ps"
	"github.com/containers/podman/v5/pkg/signal"
	"github.com/containers/podman/v5/pkg/util"
	dockerBackend "github.com/docker/docker/api/types/backend"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/storage"
	"github.com/docker/go-connections/nat"
	"github.com/docker/go-units"
	spec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"
)

func RemoveContainer(w http.ResponseWriter, r *http.Request) {
	decoder := utils.GetDecoder(r)
	query := struct {
		Force         bool  `schema:"force"`
		Ignore        bool  `schema:"ignore"`
		Depend        bool  `schema:"depend"`
		Link          bool  `schema:"link"`
		Timeout       *uint `schema:"timeout"`
		DockerVolumes bool  `schema:"v"`
		LibpodVolumes bool  `schema:"volumes"`
	}{
		// override any golang type defaults
	}

	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
		utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
		return
	}

	options := entities.RmOptions{
		Force:  query.Force,
		Ignore: query.Ignore,
	}
	if utils.IsLibpodRequest(r) {
		options.Volumes = query.LibpodVolumes
		options.Timeout = query.Timeout
		options.Depend = query.Depend
	} else {
		if query.Link {
			utils.Error(w, http.StatusBadRequest, utils.ErrLinkNotSupport)
			return
		}
		options.Volumes = query.DockerVolumes
	}

	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
	// Now use the ABI implementation to prevent us from having duplicate
	// code.
	containerEngine := abi.ContainerEngine{Libpod: runtime}
	name := utils.GetName(r)
	reports, err := containerEngine.ContainerRm(r.Context(), []string{name}, options)
	if err != nil {
		if errors.Is(err, define.ErrNoSuchCtr) {
			utils.ContainerNotFound(w, name, err)
			return
		}

		utils.InternalServerError(w, err)
		return
	}
	if len(reports) > 0 && reports[0].Err != nil {
		err = reports[0].Err
		if errors.Is(err, define.ErrNoSuchCtr) {
			utils.ContainerNotFound(w, name, err)
			return
		}
		utils.InternalServerError(w, err)
		return
	}
	if utils.IsLibpodRequest(r) {
		utils.WriteResponse(w, http.StatusOK, reports)
		return
	}
	utils.WriteResponse(w, http.StatusNoContent, nil)
}

func ListContainers(w http.ResponseWriter, r *http.Request) {
	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
	decoder := utils.GetDecoder(r)
	query := struct {
		All   bool `schema:"all"`
		Limit int  `schema:"limit"`
		Size  bool `schema:"size"`
	}{
		// override any golang type defaults
	}

	filterMap, err := util.PrepareFilters(r)
	if err != nil {
		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to decode filter parameters for %s: %w", r.URL.String(), err))
		return
	}

	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
		return
	}

	filterFuncs := make([]libpod.ContainerFilter, 0, len(*filterMap))
	all := query.All || query.Limit > 0
	if len(*filterMap) > 0 {
		for k, v := range *filterMap {
			generatedFunc, err := filters.GenerateContainerFilterFuncs(k, v, runtime)
			if err != nil {
				utils.InternalServerError(w, err)
				return
			}
			filterFuncs = append(filterFuncs, generatedFunc)
		}
	}

	// Docker thinks that if status is given as an input, then we should override
	// the all setting and always deal with all containers.
	if len((*filterMap)["status"]) > 0 {
		all = true
	}
	if !all {
		runningOnly, err := filters.GenerateContainerFilterFuncs("status", []string{define.ContainerStateRunning.String()}, runtime)
		if err != nil {
			utils.InternalServerError(w, err)
			return
		}
		filterFuncs = append(filterFuncs, runningOnly)
	}

	containers, err := runtime.GetContainers(false, filterFuncs...)
	if err != nil {
		utils.InternalServerError(w, err)
		return
	}
	if _, found := r.URL.Query()["limit"]; found && query.Limit > 0 {
		// Sort the libpod containers
		sort.Sort(ps.SortCreateTime{SortContainers: containers})
		// we should perform the lopping before we start getting
		// the expensive information on containers
		if len(containers) > query.Limit {
			containers = containers[:query.Limit]
		}
	}
	list := make([]*handlers.Container, 0, len(containers))
	for _, ctnr := range containers {
		api, err := LibpodToContainer(ctnr, query.Size)
		if err != nil {
			if errors.Is(err, define.ErrNoSuchCtr) {
				// container was removed between the initial fetch of the list and conversion
				logrus.Debugf("Container %s removed between initial fetch and conversion, ignoring in output", ctnr.ID())
				continue
			}
			utils.InternalServerError(w, err)
			return
		}
		list = append(list, api)
	}
	utils.WriteResponse(w, http.StatusOK, list)
}

func GetContainer(w http.ResponseWriter, r *http.Request) {
	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
	decoder := utils.GetDecoder(r)
	query := struct {
		Size bool `schema:"size"`
	}{
		// override any golang type defaults
	}

	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
		utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
		return
	}

	name := utils.GetName(r)
	ctnr, err := runtime.LookupContainer(name)
	if err != nil {
		utils.ContainerNotFound(w, name, err)
		return
	}
	api, err := LibpodToContainerJSON(ctnr, query.Size)
	if err != nil {
		utils.InternalServerError(w, err)
		return
	}
	utils.WriteResponse(w, http.StatusOK, api)
}

func KillContainer(w http.ResponseWriter, r *http.Request) {
	// /{version}/containers/(name)/kill
	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
	decoder := utils.GetDecoder(r)
	query := struct {
		Signal string `schema:"signal"`
	}{
		Signal: "KILL",
	}
	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
		utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
		return
	}

	// Now use the ABI implementation to prevent us from having duplicate
	// code.
	containerEngine := abi.ContainerEngine{Libpod: runtime}
	name := utils.GetName(r)
	options := entities.KillOptions{
		Signal: query.Signal,
	}
	report, err := containerEngine.ContainerKill(r.Context(), []string{name}, options)
	if err != nil {
		if errors.Is(err, define.ErrCtrStateInvalid) ||
			errors.Is(err, define.ErrCtrStopped) {
			utils.Error(w, http.StatusConflict, err)
			return
		}
		if errors.Is(err, define.ErrNoSuchCtr) {
			utils.ContainerNotFound(w, name, err)
			return
		}

		utils.InternalServerError(w, err)
		return
	}

	if len(report) > 0 && report[0].Err != nil {
		if errors.Is(report[0].Err, define.ErrCtrStateInvalid) ||
			errors.Is(report[0].Err, define.ErrCtrStopped) {
			utils.Error(w, http.StatusConflict, report[0].Err)
			return
		}
		utils.InternalServerError(w, report[0].Err)
		return
	}
	// Docker waits for the container to stop if the signal is 0 or
	// SIGKILL.
	if !utils.IsLibpodRequest(r) {
		sig, err := signal.ParseSignalNameOrNumber(query.Signal)
		if err != nil {
			utils.InternalServerError(w, err)
			return
		}
		if sig == 0 || sig == syscall.SIGKILL {
			opts := entities.WaitOptions{
				Conditions: []string{define.ContainerStateExited.String(), define.ContainerStateStopped.String()},
				Interval:   time.Millisecond * 250,
			}
			if _, err := containerEngine.ContainerWait(r.Context(), []string{name}, opts); err != nil {
				utils.Error(w, http.StatusInternalServerError, err)
				return
			}
		}
	}
	// Success
	utils.WriteResponse(w, http.StatusNoContent, nil)
}

func WaitContainer(w http.ResponseWriter, r *http.Request) {
	// /{version}/containers/(name)/wait
	utils.WaitContainerDocker(w, r)
}

func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) {
	imageID, imageName := l.Image()

	var (
		err        error
		sizeRootFs int64
		sizeRW     int64
		state      define.ContainerStatus
		status     string
	)

	if state, err = l.State(); err != nil {
		return nil, err
	}
	stateStr := state.String()

	// Some docker states are not the same as ours. This makes sure the state string stays true to the Docker API
	if state == define.ContainerStateCreated {
		stateStr = define.ContainerStateConfigured.String()
	}

	switch state {
	case define.ContainerStateConfigured, define.ContainerStateCreated:
		status = "Created"
	case define.ContainerStateStopped, define.ContainerStateExited:
		exitCode, _, err := l.ExitCode()
		if err != nil {
			return nil, err
		}
		finishedTime, err := l.FinishedTime()
		if err != nil {
			return nil, err
		}
		status = fmt.Sprintf("Exited (%d) %s ago", exitCode, units.HumanDuration(time.Since(finishedTime)))
	case define.ContainerStateRunning, define.ContainerStatePaused:
		startedTime, err := l.StartedTime()
		if err != nil {
			return nil, err
		}
		status = fmt.Sprintf("Up %s", units.HumanDuration(time.Since(startedTime)))
		if state == define.ContainerStatePaused {
			status += " (Paused)"
		}
	case define.ContainerStateRemoving:
		status = "Removal In Progress"
	case define.ContainerStateStopping:
		status = "Stopping"
	default:
		status = "Unknown"
	}

	if sz {
		if sizeRW, err = l.RWSize(); err != nil {
			return nil, err
		}
		if sizeRootFs, err = l.RootFsSize(); err != nil {
			return nil, err
		}
	}

	inspect, err := l.Inspect(false)
	if err != nil {
		return nil, err
	}

	ports := []container.Port{}
	for portKey, bindings := range inspect.NetworkSettings.Ports {
		portNum, proto, ok := strings.Cut(portKey, "/")
		if !ok {
			return nil, fmt.Errorf("PORT/PROTOCOL format required for %q", portKey)
		}

		containerPort, err := strconv.Atoi(portNum)
		if err != nil {
			return nil, err
		}

		if len(bindings) == 0 {
			// Exposed but not published
			ports = append(ports, container.Port{
				PrivatePort: uint16(containerPort),
				Type:        proto,
			})
		} else {
			for _, b := range bindings {
				hostPortInt, err := strconv.Atoi(b.HostPort)
				if err != nil {
					return nil, fmt.Errorf("invalid HostPort: %v", err)
				}

				ports = append(ports, container.Port{
					IP:          b.HostIP,
					PrivatePort: uint16(containerPort),
					PublicPort:  uint16(hostPortInt),
					Type:        proto,
				})
			}
		}
	}

	n, err := json.Marshal(inspect.NetworkSettings)
	if err != nil {
		return nil, err
	}
	networkSettings := container.NetworkSettingsSummary{}
	if err := json.Unmarshal(n, &networkSettings); err != nil {
		return nil, err
	}

	m, err := json.Marshal(inspect.Mounts)
	if err != nil {
		return nil, err
	}
	mounts := []container.MountPoint{}
	if err := json.Unmarshal(m, &mounts); err != nil {
		return nil, err
	}

	return &handlers.Container{
		Container: container.Summary{
			ID:         l.ID(),
			Names:      []string{fmt.Sprintf("/%s", l.Name())},
			Image:      imageName,
			ImageID:    "sha256:" + imageID,
			Command:    strings.Join(l.Command(), " "),
			Created:    l.CreatedTime().Unix(),
			Ports:      ports,
			SizeRw:     sizeRW,
			SizeRootFs: sizeRootFs,
			Labels:     l.Labels(),
			State:      stateStr,
			Status:     status,
			// FIXME: this seems broken, the field is never shown in the API output.
			HostConfig: struct {
				NetworkMode string            `json:",omitempty"`
				Annotations map[string]string `json:",omitempty"`
			}{
				NetworkMode: "host",
				// TODO: add annotations here for >= v1.46
			},
			NetworkSettings: &networkSettings,
			Mounts:          mounts,
		},
		ContainerCreateConfig: dockerBackend.ContainerCreateConfig{},
	}, nil
}

func convertSecondaryIPPrefixLen(input *define.InspectNetworkSettings, output *container.NetworkSettings) {
	for index, ip := range input.SecondaryIPAddresses {
		output.SecondaryIPAddresses[index].PrefixLen = ip.PrefixLength
	}
	for index, ip := range input.SecondaryIPv6Addresses {
		output.SecondaryIPv6Addresses[index].PrefixLen = ip.PrefixLength
	}
}

func LibpodToContainerJSON(l *libpod.Container, sz bool) (*container.InspectResponse, error) {
	imageID, imageName := l.Image()
	inspect, err := l.Inspect(sz)
	if err != nil {
		return nil, err
	}
	// Docker uses UTC
	if inspect != nil && inspect.State != nil {
		inspect.State.StartedAt = inspect.State.StartedAt.UTC()
		inspect.State.FinishedAt = inspect.State.FinishedAt.UTC()
	}
	i, err := json.Marshal(inspect.State)
	if err != nil {
		return nil, err
	}
	state := container.State{}
	if err := json.Unmarshal(i, &state); err != nil {
		return nil, err
	}

	// docker considers paused to be running
	if state.Paused {
		state.Running = true
	}

	// map our statuses to Docker's statuses
	switch state.Status {
	case define.ContainerStateConfigured.String(), define.ContainerStateCreated.String():
		state.Status = "created"
	case define.ContainerStateRunning.String(), define.ContainerStateStopping.String():
		state.Status = "running"
	case define.ContainerStatePaused.String():
		state.Status = "paused"
	case define.ContainerStateRemoving.String():
		state.Status = "removing"
	case define.ContainerStateStopped.String(), define.ContainerStateExited.String():
		state.Status = "exited"
	default:
		state.Status = "" // unknown state
	}

	if l.HasHealthCheck() && state.Status != "created" {
		state.Health = &container.Health{}
		if inspect.State.Health != nil {
			state.Health.Status = inspect.State.Health.Status
			state.Health.FailingStreak = inspect.State.Health.FailingStreak
			log := inspect.State.Health.Log

			for _, item := range log {
				res := &container.HealthcheckResult{}
				s, err := time.Parse(time.RFC3339Nano, item.Start)
				if err != nil {
					return nil, err
				}
				e, err := time.Parse(time.RFC3339Nano, item.End)
				if err != nil {
					return nil, err
				}
				res.Start = s
				res.End = e
				res.ExitCode = item.ExitCode
				res.Output = item.Output
				state.Health.Log = append(state.Health.Log, res)
			}
		}
	}

	formatCapabilities(inspect.HostConfig.CapDrop)
	formatCapabilities(inspect.HostConfig.CapAdd)

	h, err := json.Marshal(inspect.HostConfig)
	if err != nil {
		return nil, err
	}
	hc := container.HostConfig{}
	if err := json.Unmarshal(h, &hc); err != nil {
		return nil, err
	}
	sort.Strings(hc.Binds)

	// Map CgroupMode to CgroupnsMode for Docker API compatibility
	switch inspect.HostConfig.CgroupMode {
	case "private":
		hc.CgroupnsMode = container.CgroupnsModePrivate
	case "host":
		hc.CgroupnsMode = container.CgroupnsModeHost
	}

	// k8s-file == json-file
	if hc.LogConfig.Type == define.KubernetesLogging {
		hc.LogConfig.Type = define.JSONLogging
	}

	graphDriver := storage.DriverData{
		Name: inspect.GraphDriver.Name,
		Data: inspect.GraphDriver.Data,
	}

	cb := container.ContainerJSONBase{
		ID:              l.ID(),
		Created:         l.CreatedTime().UTC().Format(time.RFC3339Nano), // Docker uses UTC
		Path:            inspect.Path,
		Args:            inspect.Args,
		State:           &state,
		Image:           "sha256:" + imageID,
		ResolvConfPath:  inspect.ResolvConfPath,
		HostnamePath:    inspect.HostnamePath,
		HostsPath:       inspect.HostsPath,
		LogPath:         l.LogPath(),
		Name:            fmt.Sprintf("/%s", l.Name()),
		RestartCount:    int(inspect.RestartCount),
		Driver:          inspect.Driver,
		Platform:        "linux",
		MountLabel:      inspect.MountLabel,
		ProcessLabel:    inspect.ProcessLabel,
		AppArmorProfile: inspect.AppArmorProfile,
		ExecIDs:         inspect.ExecIDs,
		HostConfig:      &hc,
		GraphDriver:     graphDriver,
		SizeRw:          inspect.SizeRw,
		SizeRootFs:      &inspect.SizeRootFs,
	}

	// set Path and Args
	processArgs := l.Config().Spec.Process.Args
	if len(processArgs) > 0 {
		cb.Path = processArgs[0]
	}
	if len(processArgs) > 1 {
		cb.Args = processArgs[1:]
	}
	stopTimeout := int(l.StopTimeout())

	var healthcheck *container.HealthConfig
	if inspect.Config.Healthcheck != nil {
		healthcheck = &container.HealthConfig{
			Test:        inspect.Config.Healthcheck.Test,
			Interval:    inspect.Config.Healthcheck.Interval,
			Timeout:     inspect.Config.Healthcheck.Timeout,
			StartPeriod: inspect.Config.Healthcheck.StartPeriod,
			Retries:     inspect.Config.Healthcheck.Retries,
		}
	}

	// Apparently the compiler can't convert a map[string]struct{} into a nat.PortSet
	// (Despite a nat.PortSet being that exact struct with some types added)
	var exposedPorts nat.PortSet
	if len(inspect.Config.ExposedPorts) > 0 {
		exposedPorts = make(nat.PortSet)
		for p := range inspect.Config.ExposedPorts {
			exposedPorts[nat.Port(p)] = struct{}{}
		}
	}

	config := container.Config{
		Hostname:        l.Hostname(),
		Domainname:      inspect.Config.DomainName,
		User:            l.User(),
		AttachStdin:     inspect.Config.AttachStdin,
		AttachStdout:    inspect.Config.AttachStdout,
		AttachStderr:    inspect.Config.AttachStderr,
		ExposedPorts:    exposedPorts,
		Tty:             inspect.Config.Tty,
		OpenStdin:       inspect.Config.OpenStdin,
		StdinOnce:       inspect.Config.StdinOnce,
		Env:             inspect.Config.Env,
		Cmd:             l.Command(),
		Healthcheck:     healthcheck,
		ArgsEscaped:     false,
		Image:           imageName,
		Volumes:         nil,
		WorkingDir:      l.WorkingDir(),
		Entrypoint:      l.Entrypoint(),
		NetworkDisabled: false,
		MacAddress:      "",
		OnBuild:         nil,
		Labels:          l.Labels(),
		StopSignal:      strconv.Itoa(int(l.StopSignal())),
		StopTimeout:     &stopTimeout,
		Shell:           nil,
	}

	m, err := json.Marshal(inspect.Mounts)
	if err != nil {
		return nil, err
	}
	mounts := []container.MountPoint{}
	if err := json.Unmarshal(m, &mounts); err != nil {
		return nil, err
	}

	p, err := json.Marshal(inspect.NetworkSettings.Ports)
	if err != nil {
		return nil, err
	}
	ports := nat.PortMap{}
	if err := json.Unmarshal(p, &ports); err != nil {
		return nil, err
	}

	n, err := json.Marshal(inspect.NetworkSettings)
	if err != nil {
		return nil, err
	}

	networkSettings := container.NetworkSettings{}
	if err := json.Unmarshal(n, &networkSettings); err != nil {
		return nil, err
	}

	convertSecondaryIPPrefixLen(inspect.NetworkSettings, &networkSettings)

	// do not report null instead use an empty map
	if networkSettings.Networks == nil {
		networkSettings.Networks = map[string]*network.EndpointSettings{}
	}

	c := container.InspectResponse{
		ContainerJSONBase: &cb,
		Mounts:            mounts,
		Config:            &config,
		NetworkSettings:   &networkSettings,
	}
	return &c, nil
}

func formatCapabilities(slice []string) {
	for i := range slice {
		slice[i] = strings.TrimPrefix(slice[i], "CAP_")
	}
}

func RenameContainer(w http.ResponseWriter, r *http.Request) {
	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
	decoder := utils.GetDecoder(r)

	name := utils.GetName(r)
	query := struct {
		Name string `schema:"name"`
	}{}
	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
		utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
		return
	}

	ctr, err := runtime.LookupContainer(name)
	if err != nil {
		utils.ContainerNotFound(w, name, err)
		return
	}

	if _, err := runtime.RenameContainer(r.Context(), ctr, query.Name); err != nil {
		if errors.Is(err, define.ErrPodExists) || errors.Is(err, define.ErrCtrExists) {
			utils.Error(w, http.StatusConflict, err)
			return
		}
		utils.InternalServerError(w, err)
		return
	}

	utils.WriteResponse(w, http.StatusNoContent, nil)
}

func UpdateContainer(w http.ResponseWriter, r *http.Request) {
	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
	name := utils.GetName(r)

	ctr, err := runtime.LookupContainer(name)
	if err != nil {
		utils.ContainerNotFound(w, name, err)
		return
	}

	options := new(container.UpdateConfig)
	if err := json.NewDecoder(r.Body).Decode(options); err != nil {
		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decoding request body: %w", err))
		return
	}

	// Only handle the bits of update that Docker uses as examples.
	// For example, the update API claims to be able to update devices for
	// existing containers... Which I am very dubious about.
	// Ignore bits like that unless someone asks us for them.

	// We're going to be editing this, so we have to deep-copy to not affect
	// the container's own resources
	resources := new(spec.LinuxResources)
	oldResources := ctr.LinuxResources()
	if oldResources != nil {
		if err := libpod.JSONDeepCopy(oldResources, resources); err != nil {
			utils.Error(w, http.StatusInternalServerError, fmt.Errorf("copying old resource limits: %w", err))
			return
		}
	}

	// CPU limits
	cpu := resources.CPU
	if cpu == nil {
		cpu = new(spec.LinuxCPU)
	}
	useCPU := false
	if options.CPUShares != 0 {
		shares := uint64(options.CPUShares)
		cpu.Shares = &shares
		useCPU = true
	}
	if options.CPUPeriod != 0 {
		period := uint64(options.CPUPeriod)
		cpu.Period = &period
		useCPU = true
	}
	if options.CPUQuota != 0 {
		cpu.Quota = &options.CPUQuota
		useCPU = true
	}
	if options.CPURealtimeRuntime != 0 {
		cpu.RealtimeRuntime = &options.CPURealtimeRuntime
		useCPU = true
	}
	if options.CPURealtimePeriod != 0 {
		period := uint64(options.CPURealtimePeriod)
		cpu.RealtimePeriod = &period
		useCPU = true
	}
	if options.CpusetCpus != "" {
		cpu.Cpus = options.CpusetCpus
		useCPU = true
	}
	if options.CpusetMems != "" {
		cpu.Mems = options.CpusetMems
		useCPU = true
	}
	if useCPU {
		resources.CPU = cpu
	}

	// Memory limits
	mem := resources.Memory
	if mem == nil {
		mem = new(spec.LinuxMemory)
	}
	useMem := false
	if options.Memory != 0 {
		mem.Limit = &options.Memory
		useMem = true
	}
	if options.MemorySwap != 0 {
		mem.Swap = &options.MemorySwap
		useMem = true
	}
	if options.MemoryReservation != 0 {
		mem.Reservation = &options.MemoryReservation
		useMem = true
	}
	if useMem {
		resources.Memory = mem
	}

	// PIDs limit
	if options.PidsLimit != nil {
		if resources.Pids == nil {
			resources.Pids = new(spec.LinuxPids)
		}
		resources.Pids.Limit = *options.PidsLimit
	}

	// Blkio Weight
	if options.BlkioWeight != 0 {
		if resources.BlockIO == nil {
			resources.BlockIO = new(spec.LinuxBlockIO)
		}
		resources.BlockIO.Weight = &options.BlkioWeight
	}

	// Restart policy
	localPolicy := string(options.RestartPolicy.Name)
	restartPolicy := &localPolicy

	var restartRetries *uint
	if options.RestartPolicy.MaximumRetryCount != 0 {
		localRetries := uint(options.RestartPolicy.MaximumRetryCount)
		restartRetries = &localRetries
	}

	updateOptions := &entities.ContainerUpdateOptions{
		Resources:                       resources,
		ChangedHealthCheckConfiguration: &define.UpdateHealthCheckConfig{},
		RestartPolicy:                   restartPolicy,
		RestartRetries:                  restartRetries,
	}

	if err := ctr.Update(updateOptions); err != nil {
		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating container: %w", err))
		return
	}

	responseStruct := container.UpdateResponse{}
	utils.WriteResponse(w, http.StatusOK, responseStruct)
}
