package service

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/app/task"
	"github.com/1Panel-dev/1Panel/agent/global"
	"github.com/1Panel-dev/1Panel/agent/i18n"
	"github.com/1Panel-dev/1Panel/agent/utils/docker"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/mount"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
)

func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	ctx := context.Background()

	taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeContainer, req.TaskID, 1)
	if err != nil {
		_ = client.Close()
		global.LOG.Errorf("new task for create container failed, err: %v", err)
		return err
	}
	go func() {
		defer client.Close()
		taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
			if !checkImageExist(client, req.Image) || req.ForcePull {
				if err := pullImages(taskItem, client, req.Image); err != nil {
					if !req.ForcePull {
						return err
					}
					return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
				}
			}
			return nil
		}, nil)

		taskItem.AddSubTaskWithOps(task.GetTaskName(req.Name, task.TaskUpdate, task.TaskScopeContainer), func(t *task.Task) error {
			t.LogStart(i18n.GetWithName("ContainerAcquireLock", req.Name))
			unlock := containerOperationLock.lock(req.Name)
			defer unlock()
			t.LogWithStatus(i18n.GetWithName("ContainerAcquireLock", req.Name), nil)

			oldContainer, err := client.ContainerInspect(ctx, req.Name)
			if err != nil {
				taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
				return err
			}
			config, hostConf, networkConf, err := loadConfigInfo(false, req, &oldContainer)
			taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
			if err != nil {
				return err
			}
			normalizeContainerEndpointSettings(ctx, client, networkConf, nil)

			cleanupErr, err := switchContainer(ctx, client, req.Name, oldContainer, func() (container.CreateResponse, error) {
				return createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
					return client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
				}, networkConf.EndpointsConfig, oldContainer.NetworkSettings)
			}, newContainerSwitchTaskLogger(t))
			if err != nil {
				return fmt.Errorf("update container failed, err: %v", err)
			}
			if cleanupErr != nil {
				taskItem.Log(i18n.GetWithNameAndErr("ContainerCleanupWarning", containerSwitchBackupName(oldContainer.ID), cleanupErr))
			}
			return nil
		}, nil, 0, 0)

		if err := taskItem.Execute(); err != nil {
			global.LOG.Error(err.Error())
		}
	}()

	return nil
}

func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	ctx := context.Background()
	taskItem, err := task.NewTaskWithOps(req.Image, task.TaskUpgrade, task.TaskScopeImage, req.TaskID, 1)
	if err != nil {
		_ = client.Close()
		global.LOG.Errorf("new task for create container failed, err: %v", err)
		return err
	}
	go func() {
		defer client.Close()
		taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
			taskItem.LogStart(i18n.GetWithName("ContainerImagePull", req.Image))
			if !checkImageExist(client, req.Image) || req.ForcePull {
				if err := pullImages(taskItem, client, req.Image); err != nil {
					if !req.ForcePull {
						return err
					}
					return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
				}
			}
			return nil
		}, nil)
		var upgradeErrors []error
		for _, item := range req.Names {
			item := item
			taskItem.AddSubTaskWithIgnoreErr(i18n.GetWithName("ContainerUpgradeItem", item), func(t *task.Task) error {
				t.Logf("----------------- %s -----------------", item)
				t.LogStart(i18n.GetWithName("ContainerAcquireLock", item))
				unlock := containerOperationLock.lock(item)
				defer unlock()
				t.LogWithStatus(i18n.GetWithName("ContainerAcquireLock", item), nil)

				oldContainer, inspectErr := client.ContainerInspect(ctx, item)
				t.LogWithStatus(i18n.GetWithName("ContainerLoadInfo", item), inspectErr)
				if inspectErr != nil {
					err := fmt.Errorf("reload container %s failed: %w", item, inspectErr)
					upgradeErrors = append(upgradeErrors, err)
					return err
				}
				config := cloneContainerConfig(oldContainer.Config)
				config.Image = req.Image
				hostConf := cloneContainerHostConfig(oldContainer.HostConfig)
				preserveContainerVolumeMounts(hostConf, oldContainer.Mounts)
				cleanupErr, err := switchContainer(ctx, client, item, oldContainer, func() (container.CreateResponse, error) {
					return createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item)
				}, newContainerSwitchTaskLogger(t))
				if err != nil {
					upgradeErr := fmt.Errorf("upgrade container %s failed: %w", item, err)
					upgradeErrors = append(upgradeErrors, upgradeErr)
					return upgradeErr
				}
				if cleanupErr != nil {
					t.Log(i18n.GetWithNameAndErr("ContainerCleanupWarning", containerSwitchBackupName(oldContainer.ID), cleanupErr))
				}
				return nil
			})
		}
		taskItem.AddSubTask(i18n.GetMsgByKey("ContainerUpgradeSummary"), func(t *task.Task) error {
			return errors.Join(upgradeErrors...)
		}, nil)
		if err := taskItem.Execute(); err != nil {
			global.LOG.Error(err.Error())
		}
	}()

	return nil
}

type containerSwitchClient interface {
	ContainerStop(context.Context, string, container.StopOptions) error
	ContainerRename(context.Context, string, string) error
	ContainerStart(context.Context, string, container.StartOptions) error
	ContainerRemove(context.Context, string, container.RemoveOptions) error
	ContainerInspect(context.Context, string) (container.InspectResponse, error)
	NetworkConnect(context.Context, string, string, *network.EndpointSettings) error
	NetworkDisconnect(context.Context, string, string, bool) error
}

type containerInspectClient interface {
	ContainerInspect(context.Context, string) (container.InspectResponse, error)
}

type containerOperationMutex struct {
	mutex sync.Mutex
	locks map[string]*containerOperationLockEntry
}

var containerOperationLock containerOperationMutex

type containerOperationLockEntry struct {
	mutex      sync.Mutex
	references int
}

func (l *containerOperationMutex) lock(names ...string) func() {
	nameSet := make(map[string]struct{}, len(names))
	for _, name := range names {
		if name != "" {
			nameSet[name] = struct{}{}
		}
	}
	orderedNames := make([]string, 0, len(nameSet))
	for name := range nameSet {
		orderedNames = append(orderedNames, name)
	}
	sort.Strings(orderedNames)
	if len(orderedNames) == 0 {
		return func() {}
	}

	l.mutex.Lock()
	if l.locks == nil {
		l.locks = make(map[string]*containerOperationLockEntry)
	}
	entries := make([]*containerOperationLockEntry, 0, len(orderedNames))
	for _, name := range orderedNames {
		entry := l.locks[name]
		if entry == nil {
			entry = &containerOperationLockEntry{}
			l.locks[name] = entry
		}
		entry.references++
		entries = append(entries, entry)
	}
	l.mutex.Unlock()

	for _, entry := range entries {
		entry.mutex.Lock()
	}
	return func() {
		for index := len(entries) - 1; index >= 0; index-- {
			entries[index].mutex.Unlock()
		}
		l.mutex.Lock()
		defer l.mutex.Unlock()
		for index, name := range orderedNames {
			entry := entries[index]
			entry.references--
			if entry.references == 0 {
				delete(l.locks, name)
			}
		}
	}
}

type containerNetworkAttachment struct {
	name      string
	endpoint  *network.EndpointSettings
	isDynamic bool
}

type containerSwitchLogFunc func(messageKey, containerName string, err error)

func newContainerSwitchTaskLogger(t *task.Task) containerSwitchLogFunc {
	return func(messageKey, containerName string, err error) {
		t.LogWithStatus(i18n.GetWithName(messageKey, containerName), err)
	}
}

func logContainerSwitchStep(logger containerSwitchLogFunc, messageKey, containerName string, err error) {
	if logger != nil {
		logger(messageKey, containerName, err)
	}
}

// switchContainer keeps the stopped original container as the rollback target until the replacement starts.
func switchContainer(
	ctx context.Context,
	cli containerSwitchClient,
	name string,
	oldContainer container.InspectResponse,
	createNew func() (container.CreateResponse, error),
	logger containerSwitchLogFunc,
) (cleanupErr error, err error) {
	if oldContainer.ID == "" {
		return nil, fmt.Errorf("original container ID is empty")
	}
	wasRunning := oldContainer.State != nil && oldContainer.State.Running
	if wasRunning && oldContainer.HostConfig != nil && oldContainer.HostConfig.AutoRemove {
		return nil, fmt.Errorf("cannot safely replace container %s with auto-remove enabled", name)
	}

	if wasRunning {
		if err := cli.ContainerStop(ctx, oldContainer.ID, container.StopOptions{}); err != nil {
			logContainerSwitchStep(logger, "ContainerStopOld", name, err)
			current, inspectErr := cli.ContainerInspect(ctx, oldContainer.ID)
			if inspectErr == nil && current.State != nil && !current.State.Running {
				restartErr := restartOriginalContainer(ctx, cli, oldContainer.ID)
				logContainerSwitchStep(logger, "ContainerRollbackRestartOld", name, restartErr)
				return nil, errors.Join(fmt.Errorf("stop original container failed: %w", err), restartErr)
			}
			return nil, fmt.Errorf("stop original container failed: %w", err)
		}
		logContainerSwitchStep(logger, "ContainerStopOld", name, nil)
	}

	backupName := containerSwitchBackupName(oldContainer.ID)
	if err := cli.ContainerRename(ctx, oldContainer.ID, backupName); err != nil {
		logContainerSwitchStep(logger, "ContainerRenameOld", name, err)
		if wasRunning {
			restartErr := restartOriginalContainer(ctx, cli, oldContainer.ID)
			logContainerSwitchStep(logger, "ContainerRollbackRestartOld", name, restartErr)
			return nil, errors.Join(fmt.Errorf("rename original container failed: %w", err), restartErr)
		}
		return nil, fmt.Errorf("rename original container failed: %w", err)
	}
	logContainerSwitchStep(logger, "ContainerRenameOld", name, nil)
	disconnectedNetworks, disconnectErr := disconnectOriginalContainerNetworks(ctx, cli, oldContainer)
	logContainerSwitchStep(logger, "ContainerDisconnectOld", backupName, disconnectErr)
	if disconnectErr != nil {
		rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, "", disconnectedNetworks, logger)
		return nil, errors.Join(disconnectErr, rollbackErr)
	}

	created, createErr := createNew()
	logContainerSwitchStep(logger, "ContainerCreateReplacement", name, createErr)
	if createErr != nil {
		rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
		return nil, errors.Join(createErr, rollbackErr)
	}
	if err := cli.ContainerStart(ctx, created.ID, container.StartOptions{}); err != nil {
		logContainerSwitchStep(logger, "ContainerStartReplacement", name, err)
		rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
		return nil, errors.Join(fmt.Errorf("start new container failed: %w", err), rollbackErr)
	}
	logContainerSwitchStep(logger, "ContainerStartReplacement", name, nil)
	if wasRunning {
		if err := waitContainerReady(ctx, cli, created.ID); err != nil {
			logContainerSwitchStep(logger, "ContainerWaitReplacement", name, err)
			rollbackErr := restoreOriginalContainer(ctx, cli, oldContainer.ID, name, wasRunning, created.ID, disconnectedNetworks, logger)
			return nil, errors.Join(fmt.Errorf("new container readiness check failed: %w", err), rollbackErr)
		}
		logContainerSwitchStep(logger, "ContainerWaitReplacement", name, nil)
	}

	cleanupErr = cli.ContainerRemove(ctx, oldContainer.ID, container.RemoveOptions{Force: true, RemoveVolumes: false})
	logContainerSwitchStep(logger, "ContainerRemoveOld", backupName, cleanupErr)
	return cleanupErr, nil
}

const (
	containerStartStabilization = 10 * time.Second
	containerStartPollInterval  = time.Second
	containerHealthCheckMinWait = 30 * time.Second
	containerHealthCheckMaxWait = 10 * time.Minute
)

func waitContainerReady(ctx context.Context, cli containerInspectClient, containerID string) error {
	info, err := cli.ContainerInspect(ctx, containerID)
	if err != nil {
		return err
	}
	if err := checkContainerRunningState(info); err != nil {
		return err
	}
	if info.State.Health == nil {
		return waitContainerStable(ctx, cli, containerID, info)
	}

	initialRestartCount := info.RestartCount
	timeout := containerHealthCheckTimeout(info.Config)
	deadline := time.NewTimer(timeout)
	ticker := time.NewTicker(time.Second)
	defer deadline.Stop()
	defer ticker.Stop()
	for {
		if info.State.Restarting || info.RestartCount != initialRestartCount {
			return fmt.Errorf("container restart count changed from %d to %d during startup", initialRestartCount, info.RestartCount)
		}
		if info.State.Health == nil {
			return fmt.Errorf("container health status is unavailable")
		}
		switch info.State.Health.Status {
		case container.Healthy:
			return nil
		case container.Unhealthy:
			return fmt.Errorf("container health status is unhealthy")
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-deadline.C:
			return fmt.Errorf("container health check timed out after %s", timeout)
		case <-ticker.C:
			info, err = cli.ContainerInspect(ctx, containerID)
			if err != nil {
				return err
			}
			if err := checkContainerRunningState(info); err != nil {
				return err
			}
		}
	}
}

func waitContainerStable(ctx context.Context, cli containerInspectClient, containerID string, initial container.InspectResponse) error {
	startedAt := initial.State.StartedAt
	restartCount := initial.RestartCount
	if err := checkContainerStableState(initial, startedAt, restartCount); err != nil {
		return err
	}
	deadline := time.NewTimer(containerStartStabilization)
	ticker := time.NewTicker(containerStartPollInterval)
	defer deadline.Stop()
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-deadline.C:
			info, err := cli.ContainerInspect(ctx, containerID)
			if err != nil {
				return err
			}
			return checkContainerStableState(info, startedAt, restartCount)
		case <-ticker.C:
			info, err := cli.ContainerInspect(ctx, containerID)
			if err != nil {
				return err
			}
			if err := checkContainerStableState(info, startedAt, restartCount); err != nil {
				return err
			}
		}
	}
}

func checkContainerStableState(info container.InspectResponse, startedAt string, restartCount int) error {
	if err := checkContainerRunningState(info); err != nil {
		return err
	}
	if info.State.Restarting || info.RestartCount != restartCount {
		return fmt.Errorf("container restart count changed from %d to %d during startup", restartCount, info.RestartCount)
	}
	if startedAt != "" && info.State.StartedAt != startedAt {
		return fmt.Errorf("container start time changed during startup")
	}
	return nil
}

func checkContainerRunningState(info container.InspectResponse) error {
	if info.State == nil {
		return fmt.Errorf("container state is unavailable")
	}
	if !info.State.Running {
		return fmt.Errorf("container exited with code %d: %s", info.State.ExitCode, info.State.Error)
	}
	return nil
}

func containerHealthCheckTimeout(config *container.Config) time.Duration {
	if config == nil || config.Healthcheck == nil {
		return containerHealthCheckMinWait
	}
	health := config.Healthcheck
	interval := health.Interval
	if interval <= 0 {
		interval = 30 * time.Second
	}
	checkTimeout := health.Timeout
	if checkTimeout <= 0 {
		checkTimeout = 30 * time.Second
	}
	retries := health.Retries
	if retries <= 0 {
		retries = 3
	}
	timeout := health.StartPeriod + time.Duration(retries)*(interval+checkTimeout)
	if timeout < containerHealthCheckMinWait {
		return containerHealthCheckMinWait
	}
	if timeout > containerHealthCheckMaxWait {
		return containerHealthCheckMaxWait
	}
	return timeout
}

func preserveContainerVolumeMounts(hostConfig *container.HostConfig, oldMounts []container.MountPoint) {
	if hostConfig == nil {
		return
	}
	for _, oldMount := range oldMounts {
		if oldMount.Type != mount.TypeVolume || oldMount.Name == "" || oldMount.Destination == "" {
			continue
		}
		preserved := false
		for index := range hostConfig.Mounts {
			if hostConfig.Mounts[index].Target == oldMount.Destination {
				hostConfig.Mounts[index].Type = mount.TypeVolume
				hostConfig.Mounts[index].Source = oldMount.Name
				preserved = true
			}
		}
		for index, raw := range hostConfig.Binds {
			destination, mode := containerBindDestinationAndMode(raw)
			if destination != oldMount.Destination {
				continue
			}
			hostConfig.Binds[index] = oldMount.Name + ":" + oldMount.Destination
			if mode != "" {
				hostConfig.Binds[index] += ":" + mode
			}
			preserved = true
		}
		if !preserved {
			hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{
				Type:     mount.TypeVolume,
				Source:   oldMount.Name,
				Target:   oldMount.Destination,
				ReadOnly: !oldMount.RW,
			})
		}
	}
}

func containerBindDestinationAndMode(raw string) (string, string) {
	parts := strings.SplitN(raw, ":", 3)
	switch len(parts) {
	case 1:
		return parts[0], ""
	case 2:
		return parts[1], ""
	default:
		return parts[1], parts[2]
	}
}

func containerSwitchBackupName(containerID string) string {
	if len(containerID) > 12 {
		containerID = containerID[:12]
	}
	return "1panel-backup-" + containerID
}

func restartOriginalContainer(ctx context.Context, cli containerSwitchClient, containerID string) error {
	if err := cli.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
		return fmt.Errorf("restart original container failed: %w", err)
	}
	return nil
}

func disconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitchClient, oldContainer container.InspectResponse) ([]containerNetworkAttachment, error) {
	primary, extras := buildContainerRecoverNetworkConfig(oldContainer.NetworkSettings, oldContainer.HostConfig)
	endpoints := make(map[string]*network.EndpointSettings, len(extras)+1)
	if primary != nil {
		for name, endpoint := range primary.EndpointsConfig {
			if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
				endpoints[name] = endpoint
			}
		}
	}
	for name, endpoint := range extras {
		if name != "bridge" && endpoint != nil && endpoint.IPAMConfig != nil {
			endpoints[name] = endpoint
		}
	}
	names := make([]string, 0, len(endpoints))
	for name := range endpoints {
		names = append(names, name)
	}
	sort.Strings(names)

	disconnected := make([]containerNetworkAttachment, 0, len(names))
	for _, name := range names {
		if err := cli.NetworkDisconnect(ctx, name, oldContainer.ID, true); err != nil {
			return disconnected, fmt.Errorf("disconnect original container from network %s failed: %w", name, err)
		}
		disconnected = append(disconnected, containerNetworkAttachment{
			name:      name,
			endpoint:  endpoints[name],
			isDynamic: isDynamicContainerNetwork(oldContainer.NetworkSettings, name),
		})
	}
	return disconnected, nil
}

func reconnectOriginalContainerNetworks(ctx context.Context, cli containerSwitchClient, containerID string, attachments []containerNetworkAttachment) error {
	var reconnectErr error
	for _, attachment := range attachments {
		err := cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
		if err != nil && attachment.isDynamic && strings.Contains(err.Error(), unsupportedUserSpecifiedIPAddress) {
			attachment.endpoint.IPAMConfig = nil
			err = cli.NetworkConnect(ctx, attachment.name, containerID, attachment.endpoint)
		}
		if err != nil {
			reconnectErr = errors.Join(reconnectErr, fmt.Errorf("reconnect original container to network %s failed: %w", attachment.name, err))
		}
	}
	return reconnectErr
}

func restoreOriginalContainer(ctx context.Context, cli containerSwitchClient, oldContainerID, originalName string, wasRunning bool, newContainer string, disconnectedNetworks []containerNetworkAttachment, logger containerSwitchLogFunc) error {
	var rollbackErr error
	backupName := containerSwitchBackupName(oldContainerID)
	if newContainer != "" {
		removeErr := cli.ContainerRemove(ctx, newContainer, container.RemoveOptions{Force: true, RemoveVolumes: true})
		if client.IsErrNotFound(removeErr) {
			removeErr = nil
		}
		logContainerSwitchStep(logger, "ContainerRollbackRemoveReplacement", originalName, removeErr)
		if removeErr != nil {
			rollbackErr = errors.Join(rollbackErr, fmt.Errorf("remove failed replacement container failed: %w", removeErr))
		}
	}
	renameErr := cli.ContainerRename(ctx, oldContainerID, originalName)
	logContainerSwitchStep(logger, "ContainerRollbackRenameOld", backupName, renameErr)
	if renameErr != nil {
		rollbackErr = errors.Join(rollbackErr, fmt.Errorf("restore original container name failed: %w", renameErr))
	}
	currentName := originalName
	if renameErr != nil {
		currentName = backupName
	}
	reconnectErr := reconnectOriginalContainerNetworks(ctx, cli, oldContainerID, disconnectedNetworks)
	logContainerSwitchStep(logger, "ContainerRollbackReconnectOld", currentName, reconnectErr)
	rollbackErr = errors.Join(rollbackErr, reconnectErr)
	if wasRunning {
		restartErr := restartOriginalContainer(ctx, cli, oldContainerID)
		logContainerSwitchStep(logger, "ContainerRollbackRestartOld", currentName, restartErr)
		rollbackErr = errors.Join(rollbackErr, restartErr)
	}
	return rollbackErr
}

func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) {
	networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf)
	normalizeContainerEndpointSettings(ctx, client, networkConf, extraNetworks)
	var primaryEndpoints map[string]*network.EndpointSettings
	if networkConf != nil {
		primaryEndpoints = networkConf.EndpointsConfig
	}

	created, err := createContainerWithDynamicIPFallback(func() (container.CreateResponse, error) {
		return client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name)
	}, primaryEndpoints, networkSettings)
	if err != nil {
		return created, err
	}

	extraNames := make([]string, 0, len(extraNetworks))
	for item := range extraNetworks {
		extraNames = append(extraNames, item)
	}
	sort.Strings(extraNames)
	for _, item := range extraNames {
		err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
		if clearUnsupportedDynamicEndpointIPAM(err, map[string]*network.EndpointSettings{item: extraNetworks[item]}, networkSettings) {
			err = client.NetworkConnect(ctx, item, created.ID, extraNetworks[item])
		}
		if err != nil {
			_ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true})
			return created, err
		}
	}
	return created, nil
}

func createContainerWithDynamicIPFallback(
	create func() (container.CreateResponse, error),
	endpoints map[string]*network.EndpointSettings,
	networkSettings *container.NetworkSettings,
) (container.CreateResponse, error) {
	for {
		created, err := create()
		if err == nil || created.ID != "" || !clearUnsupportedDynamicEndpointIPAM(err, endpoints, networkSettings) {
			return created, err
		}
	}
}
