package service

import (
	"archive/tar"
	"bufio"
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/app/repo"
	"github.com/1Panel-dev/1Panel/agent/app/task"
	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/global"
	"github.com/1Panel-dev/1Panel/agent/i18n"
	"github.com/1Panel-dev/1Panel/agent/utils/cmd"
	"github.com/1Panel-dev/1Panel/agent/utils/common"
	"github.com/1Panel-dev/1Panel/agent/utils/docker"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/build"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/image"
	"github.com/docker/docker/api/types/mount"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/registry"
	"github.com/docker/docker/api/types/volume"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/stdcopy"
	"github.com/docker/go-connections/nat"
	"github.com/gin-gonic/gin"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/shirou/gopsutil/v4/cpu"
	"github.com/shirou/gopsutil/v4/mem"
)

type ContainerService struct{}

var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")

type IContainerService interface {
	Page(req dto.PageContainer) (int64, interface{}, error)
	List() []dto.ContainerOptions
	ListByImage(imageName string) []dto.ContainerOptions
	LoadStatus() (dto.ContainerStatus, error)
	PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
	ListNetwork() ([]dto.Options, error)
	PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
	ListVolume() ([]dto.Options, error)

	PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
	LoadComposeEnv(name string) (string, error)
	CreateCompose(req dto.ComposeCreate) error
	ComposeOperation(req dto.ComposeOperation) error
	TestCompose(req dto.ComposeCreate) (bool, error)
	ComposeUpdate(req dto.ComposeUpdate) error
	ComposePin(req dto.ComposePin) error
	ComposeLogClean(req dto.ComposeLogClean) error

	ContainerCreate(req dto.ContainerOperate, inThread bool) error
	ContainerUpdate(req dto.ContainerOperate) error
	ContainerUpgrade(req dto.ContainerUpgrade) error
	ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
	ContainerListStats() ([]dto.ContainerListStats, error)
	ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
	LoadResourceLimit() (*dto.ResourceLimit, error)
	ContainerRename(req dto.ContainerRename) error
	ContainerCommit(req dto.ContainerCommit) error
	ContainerLogClean(req dto.OperationWithName) error
	ContainerOperation(req dto.ContainerOperation) error
	DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error
	ContainerStats(id string) (*dto.ContainerStats, error)

	Inspect(req dto.InspectReq) (string, error)
	DeleteNetwork(req dto.BatchDelete) error
	CreateNetwork(req dto.NetworkCreate) error
	DeleteVolume(req dto.BatchDelete) error
	CreateVolume(req dto.VolumeCreate) error
	Prune(req dto.ContainerPrune) error

	LoadUsers(req dto.OperationWithName) []string
	ListContainerFiles(req dto.ContainerFileReq) ([]dto.ContainerFileInfo, error)
	UploadContainerFile(req dto.ContainerFileReq, fileName string, fileSize int64, file io.Reader) error
	GetContainerFileContent(req dto.ContainerFileReq) (*dto.ContainerFileContent, error)
	GetContainerFileSize(req dto.ContainerFileReq) (int64, error)
	DeleteContainerFile(req dto.ContainerFileBatchDeleteReq) error
	DownloadContainerFile(req dto.ContainerFileReq) (io.ReadCloser, string, string, error)

	StreamLogs(ctx *gin.Context, params dto.StreamLog)
}

func NewIContainerService() IContainerService {
	return &ContainerService{}
}

func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
	client, err := docker.NewDockerClient()
	if err != nil {
		return 0, nil, err
	}
	defer func() { _ = client.Close() }()
	options := container.ListOptions{All: true}
	if len(req.Filters) != 0 {
		options.Filters = filters.NewArgs()
		options.Filters.Add("label", req.Filters)
	}
	containers, err := client.ContainerList(context.Background(), options)
	if err != nil {
		return 0, nil, err
	}
	records := searchWithFilter(req, containers)

	var backData []dto.ContainerInfo
	total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize
	if start > total {
		backData = make([]dto.ContainerInfo, 0)
	} else {
		if end >= total {
			end = total
		}
		backData = records[start:end]
	}

	for i := 0; i < len(backData); i++ {
		install, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(backData[i].Name))
		if install.ID > 0 {
			backData[i].AppInstallName = install.Name
			backData[i].AppName = install.App.Name
			websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(install.ID))
			for _, website := range websites {
				backData[i].Websites = append(backData[i].Websites, website.PrimaryDomain)
			}
		}
	}

	return int64(total), backData, nil
}

func (u *ContainerService) List() []dto.ContainerOptions {
	var options []dto.ContainerOptions
	client, err := docker.NewDockerClient()
	if err != nil {
		global.LOG.Errorf("load docker client for contianer list failed, err: %v", err)
		return nil
	}
	defer client.Close()
	containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
	if err != nil {
		global.LOG.Errorf("load container list failed, err: %v", err)
		return nil
	}
	for _, container := range containers {
		for _, name := range container.Names {
			if len(name) != 0 {
				options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State})
			}
		}
	}

	return options
}

func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions {
	var options []dto.ContainerOptions
	client, err := docker.NewDockerClient()
	if err != nil {
		global.LOG.Errorf("load docker client for contianer list failed, err: %v", err)
		return nil
	}
	defer client.Close()
	containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
	if err != nil {
		global.LOG.Errorf("load container list failed, err: %v", err)
		return nil
	}
	for _, container := range containers {
		if container.Image != imageName {
			continue
		}
		for _, name := range container.Names {
			if len(name) != 0 {
				options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State})
			}
		}
	}

	return options
}

func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
	var data dto.ContainerStatus
	client, err := docker.NewDockerClient()
	if err != nil {
		return data, err
	}
	defer client.Close()
	c := context.Background()

	images, _ := client.ImageList(c, image.ListOptions{All: true})
	data.ImageCount = len(images)
	repo, _ := imageRepoRepo.List()
	data.RepoCount = len(repo)
	templates, _ := composeRepo.List()
	data.ComposeTemplateCount = len(templates)
	networks, _ := client.NetworkList(c, network.ListOptions{})
	data.NetworkCount = len(networks)
	volumes, _ := client.VolumeList(c, volume.ListOptions{})
	data.VolumeCount = len(volumes.Volumes)
	data.ComposeCount = loadComposeCount(client)
	containers, _ := client.ContainerList(c, container.ListOptions{All: true})
	data.ContainerCount = len(containers)
	for _, item := range containers {
		switch item.State {
		case "created":
			data.Created++
		case "running":
			data.Running++
		case "paused":
			data.Paused++
		case "restarting":
			data.Restarting++
		case "dead":
			data.Dead++
		case "exited":
			data.Exited++
		case "removing":
			data.Removing++
		}
	}
	return data, nil
}
func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error) {
	var data dto.ContainerItemStats
	client, err := docker.NewDockerClient()
	if err != nil {
		return data, err
	}
	if req.Name != "system" {
		defer client.Close()
		containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), req.Name, true)
		if err != nil {
			return data, err
		}
		data.SizeRw = *containerInfo.SizeRw
		data.SizeRootFs = *containerInfo.SizeRootFs
		return data, nil
	}

	usage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
	if err != nil {
		return data, err
	}
	for _, item := range usage.Images {
		data.ImageUsage += item.Size
		if item.Containers < 1 {
			data.ImageReclaimable += item.Size
		}
	}
	for _, item := range usage.Containers {
		data.ContainerUsage += item.SizeRw
		if item.State != "running" {
			data.ContainerReclaimable += item.SizeRw
		}
	}
	for _, item := range usage.Volumes {
		data.VolumeUsage += item.UsageData.Size
		if item.UsageData.RefCount == 0 {
			data.VolumeReclaimable += item.UsageData.Size
		}
	}
	for _, item := range usage.BuildCache {
		data.BuildCacheUsage += item.Size
	}
	return data, nil
}
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
	client, err := docker.NewDockerClient()
	if err != nil {
		return nil, err
	}
	defer client.Close()
	list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
	if err != nil {
		return nil, err
	}
	datas := make([]dto.ContainerListStats, len(list))
	var wg sync.WaitGroup
	wg.Add(len(list))
	for i := 0; i < len(list); i++ {
		go func(index int, item container.Summary) {
			datas[index] = loadCpuAndMem(client, item.ID)
			wg.Done()
		}(i, list[i])
	}
	wg.Wait()
	return datas, nil
}

func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
	client, err := docker.NewDockerClient()
	if err != nil {
		return "", err
	}
	defer client.Close()
	var inspectInfo interface{}
	switch req.Type {
	case "container":
		inspectInfo, err = client.ContainerInspect(context.Background(), req.ID)
	case "compose":
		filePath := ""
		cli, err := docker.NewDockerClient()
		if err != nil {
			return "", err
		}
		defer cli.Close()
		options := container.ListOptions{All: true}
		options.Filters = filters.NewArgs()
		options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.ID))
		containers, err := cli.ContainerList(context.Background(), options)
		if err != nil {
			return "", err
		}
		for _, container := range containers {
			config := container.Labels[composeConfigLabel]
			if len(req.Detail) != 0 && strings.Contains(config, req.Detail) {
				config = req.Detail
			}
			workdir := container.Labels[composeWorkdirLabel]
			if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
				filePath = config
				break
			} else {
				filePath = workdir
				break
			}
		}
		if len(containers) == 0 {
			composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.ID))
			filePath = composeItem.Path
		}
		if _, err := os.Stat(filePath); err != nil {
			return "", err
		}
		content, err := os.ReadFile(filePath)
		if err != nil {
			return "", err
		}
		return string(content), nil
	case "image":
		inspectInfo, _, err = client.ImageInspectWithRaw(context.Background(), req.ID)
	case "network":
		inspectInfo, err = client.NetworkInspect(context.TODO(), req.ID, network.InspectOptions{})
	case "volume":
		inspectInfo, err = client.VolumeInspect(context.TODO(), req.ID)
	}
	if err != nil {
		return "", err
	}
	bytes, err := json.Marshal(inspectInfo)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

func (u *ContainerService) Prune(req dto.ContainerPrune) error {
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	defer client.Close()
	name := ""
	switch req.PruneType {
	case "container":
		name = "Container"
	case "image":
		name = "Image"
	case "volume":
		name = "Volume"
	case "buildcache":
		name = "BuildCache"
	case "network":
		name = "Network"
	}
	taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey(name), task.TaskClean, task.TaskScopeContainer, req.TaskID, 1)
	if err != nil {
		global.LOG.Errorf("new task for create container failed, err: %v", err)
		return err
	}

	taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
		pruneFilters := filters.NewArgs()
		if req.WithTagAll {
			pruneFilters.Add("dangling", "false")
			if req.PruneType != "image" {
				pruneFilters.Add("until", "24h")
			}
		}
		taskItem.Log(i18n.GetMsgByKey("PruneStart"))
		SpaceReclaimed := 0
		switch req.PruneType {
		case "container":
			rep, err := client.ContainersPrune(context.Background(), pruneFilters)
			if err != nil {
				return err
			}
			SpaceReclaimed = int(rep.SpaceReclaimed)
		case "image":
			rep, err := client.ImagesPrune(context.Background(), pruneFilters)
			if err != nil {
				return err
			}
			SpaceReclaimed = int(rep.SpaceReclaimed)
		case "network":
			_, err := client.NetworksPrune(context.Background(), pruneFilters)
			if err != nil {
				return err
			}
		case "volume":
			versions, err := client.ServerVersion(context.Background())
			if err != nil {
				return err
			}
			if common.ComparePanelVersion(versions.APIVersion, "1.42") {
				pruneFilters.Add("all", "true")
			}
			rep, err := client.VolumesPrune(context.Background(), pruneFilters)
			if err != nil {
				return err
			}
			SpaceReclaimed = int(rep.SpaceReclaimed)
		case "buildcache":
			opts := build.CachePruneOptions{}
			opts.All = true
			rep, err := client.BuildCachePrune(context.Background(), opts)
			if err != nil {
				return err
			}
			SpaceReclaimed = int(rep.SpaceReclaimed)
		}
		taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
		return nil
	}, nil)
	go func() {
		_ = taskItem.Execute()
	}()
	return nil
}

func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
	cpuCounts, err := cpu.Counts(true)
	if err != nil {
		return nil, fmt.Errorf("load cpu limit failed, err: %v", err)
	}
	memoryInfo, err := mem.VirtualMemory()
	if err != nil {
		return nil, fmt.Errorf("load memory limit failed, err: %v", err)
	}

	data := dto.ResourceLimit{
		CPU:    cpuCounts,
		Memory: memoryInfo.Total,
	}
	return &data, nil
}

func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	unlock := containerOperationLock.lock(req.Name)
	ctx := context.Background()
	newContainer, _ := client.ContainerInspect(ctx, req.Name)
	if newContainer.ContainerJSONBase != nil {
		unlock()
		_ = client.Close()
		return buserr.New("ErrContainerName")
	}

	taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
	if err != nil {
		unlock()
		_ = client.Close()
		global.LOG.Errorf("new task for create container failed, err: %v", err)
		return err
	}

	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 nil
	}, nil)

	taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error {
		imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
		if err != nil {
			return err
		}
		if len(req.Entrypoint) == 0 {
			req.Entrypoint = imageInfo.Config.Entrypoint
		}
		if len(req.Cmd) == 0 {
			req.Cmd = imageInfo.Config.Cmd
		}
		return nil
	}, nil)

	taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
		config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
		taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
		if err != nil {
			return err
		}
		normalizeContainerEndpointSettings(ctx, client, networkConf, nil)
		con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
		if err != nil {
			taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
			if con.ID != "" {
				_ = client.ContainerRemove(ctx, con.ID, container.RemoveOptions{RemoveVolumes: true, Force: true})
			}
			return err
		}
		err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
		taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
		if err != nil {
			taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
			_ = client.ContainerRemove(ctx, con.ID, container.RemoveOptions{RemoveVolumes: true, Force: true})
			return fmt.Errorf("create successful but start failed, err: %v", err)
		}
		return nil
	}, nil)

	if inThread {
		go func() {
			defer unlock()
			defer client.Close()
			if err := taskItem.Execute(); err != nil {
				global.LOG.Error(err.Error())
			}
		}()
		return nil
	}
	defer unlock()
	defer client.Close()
	return taskItem.Execute()
}

func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
	client, err := docker.NewDockerClient()
	if err != nil {
		return nil, err
	}
	defer client.Close()
	ctx := context.Background()
	oldContainer, err := client.ContainerInspect(ctx, req.Name)
	if err != nil {
		return nil, err
	}

	var data dto.ContainerOperate
	data.Name = strings.ReplaceAll(oldContainer.Name, "/", "")
	data.Image = oldContainer.Config.Image
	if oldContainer.NetworkSettings != nil {
		for net, val := range oldContainer.NetworkSettings.Networks {
			data.Networks = append(data.Networks, loadContainerNetworkInfo(net, val))
		}
	}

	exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
	data.ExposedPorts = loadContainerPortForInfo(exposePorts)
	data.Hostname = oldContainer.Config.Hostname
	data.DNS = oldContainer.HostConfig.DNS
	for _, item := range oldContainer.HostConfig.ExtraHosts {
		parts := strings.SplitN(item, ":", 2)
		if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 {
			continue
		}
		data.ExtraHosts = append(data.ExtraHosts, dto.ExtraHost{
			Hostname: parts[0],
			IP:       parts[1],
		})
	}
	data.DomainName = oldContainer.Config.Domainname

	data.Cmd = oldContainer.Config.Cmd
	data.WorkingDir = oldContainer.Config.WorkingDir
	data.User = oldContainer.Config.User
	data.OpenStdin = oldContainer.Config.OpenStdin
	data.Tty = oldContainer.Config.Tty
	data.Entrypoint = oldContainer.Config.Entrypoint
	data.Env = oldContainer.Config.Env
	data.CPUShares = oldContainer.HostConfig.CPUShares
	for key, val := range oldContainer.Config.Labels {
		data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
	}
	data.AutoRemove = oldContainer.HostConfig.AutoRemove
	data.Privileged = oldContainer.HostConfig.Privileged
	data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
	data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name)
	if oldContainer.HostConfig.NanoCPUs != 0 {
		data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
	}
	if oldContainer.HostConfig.Memory != 0 {
		data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
	}
	data.Volumes = loadVolumeBinds(oldContainer.Mounts)

	return &data, nil
}

func loadContainerNetworkInfo(name string, endpoint *network.EndpointSettings) dto.ContainerNetwork {
	item := dto.ContainerNetwork{Network: name}
	if endpoint == nil {
		return item
	}
	item.MacAddr = endpoint.MacAddress
	item.Links = append([]string(nil), endpoint.Links...)
	item.Aliases = append([]string(nil), endpoint.Aliases...)
	item.DriverOpts = cloneStringMap(endpoint.DriverOpts)
	item.GwPriority = endpoint.GwPriority
	if endpoint.IPAMConfig != nil {
		item.LinkLocalIPs = append([]string(nil), endpoint.IPAMConfig.LinkLocalIPs...)
	}
	if name != "bridge" {
		if endpoint.IPAMConfig != nil {
			item.Ipv4 = endpoint.IPAMConfig.IPv4Address
			item.Ipv6 = endpoint.IPAMConfig.IPv6Address
		} else {
			item.Ipv4 = endpoint.IPAddress
			item.Ipv6 = endpoint.GlobalIPv6Address
		}
	}
	return item
}

func cloneStringMap(source map[string]string) map[string]string {
	if len(source) == 0 {
		return nil
	}
	result := make(map[string]string, len(source))
	for key, value := range source {
		result[key] = value
	}
	return result
}

func (u *ContainerService) ContainerRename(req dto.ContainerRename) error {
	ctx := context.Background()
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	defer client.Close()
	unlock := containerOperationLock.lock(req.Name, req.NewName)
	defer unlock()

	newContainer, _ := client.ContainerInspect(ctx, req.NewName)
	if newContainer.ContainerJSONBase != nil {
		return buserr.New("ErrContainerName")
	}
	return client.ContainerRename(ctx, req.Name, req.NewName)
}

func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error {
	ctx := context.Background()
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	defer client.Close()
	options := container.CommitOptions{
		Reference: req.NewImageName,
		Comment:   req.Comment,
		Author:    req.Author,
		Changes:   nil,
		Pause:     req.Pause,
		Config:    nil,
	}

	taskItem, err := task.NewTaskWithOps(req.NewImageName, task.TaskCommit, task.TaskScopeContainer, req.TaskID, 1)
	if err != nil {
		return fmt.Errorf("new task for container commit failed, err: %v", err)
	}

	go func() {
		taskItem.AddSubTask(i18n.GetWithName("TaskCommit", req.NewImageName), func(t *task.Task) error {
			res, err := client.ContainerCommit(ctx, req.ContainerId, options)
			if err != nil {
				return fmt.Errorf("failed to commit container, err: %v", err)
			}
			taskItem.Log(res.ID)
			return nil
		}, nil)
		_ = taskItem.Execute()
	}()
	return nil
}

func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
	ctx := context.Background()
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, " "), req.Operation, task.TaskScopeContainer, req.TaskID, 1)
	if err != nil {
		_ = client.Close()
		return fmt.Errorf("new task for container commit failed, err: %v", err)
	}

	for _, item := range req.Names {
		item := item
		taskItem.AddSubTask(item, func(t *task.Task) error {
			unlock := containerOperationLock.lock(item)
			defer unlock()
			var operationErr error
			switch req.Operation {
			case constant.ContainerOpStart:
				operationErr = client.ContainerStart(ctx, item, container.StartOptions{})
			case constant.ContainerOpStop:
				operationErr = client.ContainerStop(ctx, item, container.StopOptions{})
			case constant.ContainerOpRestart:
				operationErr = client.ContainerRestart(ctx, item, container.StopOptions{})
			case constant.ContainerOpKill:
				operationErr = client.ContainerKill(ctx, item, "SIGKILL")
			case constant.ContainerOpPause:
				operationErr = client.ContainerPause(ctx, item)
			case constant.ContainerOpUnpause:
				operationErr = client.ContainerUnpause(ctx, item)
			case constant.ContainerOpRemove:
				operationErr = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true})
			}
			return operationErr
		}, nil)
	}

	go func() {
		defer client.Close()
		_ = taskItem.Execute()
	}()
	return nil
}

func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
	client, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	defer client.Close()
	unlock := containerOperationLock.lock(req.Name)
	defer unlock()
	ctx := context.Background()
	containerItem, err := client.ContainerInspect(ctx, req.Name)
	if err != nil {
		return err
	}
	if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil {
		return err
	}
	file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm)
	if err != nil {
		return err
	}
	defer file.Close()
	if err = file.Truncate(0); err != nil {
		return err
	}
	_, _ = file.Seek(0, 0)

	files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath))
	for _, file := range files {
		_ = os.Remove(file)
	}

	if err := client.ContainerStart(ctx, containerItem.ID, container.StartOptions{}); err != nil {
		return err
	}
	return nil
}

func (u *ContainerService) StreamLogs(ctx *gin.Context, params dto.StreamLog) {
	messageChan := make(chan string, 1024)
	errorChan := make(chan error, 1)
	doneChan := make(chan struct{})

	go func() {
		<-ctx.Request.Context().Done()
		close(doneChan)
	}()
	go collectLogs(doneChan, params, messageChan, errorChan)
	ctx.Stream(func(w io.Writer) bool {
		select {
		case msg, ok := <-messageChan:
			if !ok {
				return false
			}
			_, err := fmt.Fprintf(w, "data: %s\n\n", msg)
			if err != nil {
				return false
			}
			return true
		case err := <-errorChan:
			if err != nil {
				_, _ = fmt.Fprintf(w, "event: error\ndata: %v\n\n", err.Error())
			}
			return false
		}
	})
}

func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<- string, errorChan chan<- error) {
	defer close(messageChan)
	defer close(errorChan)
	var cmdArgs []string
	cmdArgs = append(cmdArgs, "logs")
	if params.Follow {
		cmdArgs = append(cmdArgs, "-f")
	}
	if params.Timestamp {
		cmdArgs = append(cmdArgs, "-t")
	}
	if params.Tail != "0" {
		cmdArgs = append(cmdArgs, "--tail", params.Tail)
	}
	if params.Since != "all" {
		cmdArgs = append(cmdArgs, "--since", params.Since)
	}
	if params.Container != "" {
		cmdArgs = append(cmdArgs, params.Container)
	}

	var dockerCmd *exec.Cmd
	if params.Type == "compose" {
		dockerComposeCmd := common.GetDockerComposeCommand()
		var yamlFiles []string
		for _, item := range strings.Split(params.Compose, ",") {
			if len(item) != 0 {
				yamlFiles = append(yamlFiles, "-f", item)
			}
		}
		if dockerComposeCmd == "docker-compose" {
			newCmdArgs := append(yamlFiles, cmdArgs...)
			dockerCmd = exec.Command(dockerComposeCmd, newCmdArgs...)
		} else {
			newCmdArgs := append(append([]string{"compose"}, yamlFiles...), cmdArgs...)
			dockerCmd = exec.Command("docker", newCmdArgs...)
		}
	} else {
		dockerCmd = exec.Command("docker", cmdArgs...)
	}

	dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	stdout, err := dockerCmd.StdoutPipe()
	if err != nil {
		errorChan <- fmt.Errorf("failed to get stdout pipe: %v", err)
		return
	}

	dockerCmd.Stderr = dockerCmd.Stdout

	if err = dockerCmd.Start(); err != nil {
		errorChan <- fmt.Errorf("failed to start docker logs command: %v", err)
		return
	}

	defer func() {
		if dockerCmd.Process != nil {
			if pgid, err := syscall.Getpgid(dockerCmd.Process.Pid); err == nil {
				_ = syscall.Kill(-pgid, syscall.SIGKILL)
			}
			_ = dockerCmd.Process.Kill()
			_ = dockerCmd.Wait()
		}
	}()

	reader := bufio.NewReader(stdout)

	processKilled := false
	go func() {
		<-done
		if !processKilled && dockerCmd.Process != nil {
			processKilled = true
			if pgid, err := syscall.Getpgid(dockerCmd.Process.Pid); err == nil {
				_ = syscall.Kill(-pgid, syscall.SIGKILL)
			}
			_ = dockerCmd.Process.Kill()
		}
	}()

	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				if len(line) > 0 {
					line = strings.TrimSuffix(line, "\n")
					select {
					case messageChan <- line:
					case <-done:
						return
					}
				}
				break
			}
			errorChan <- fmt.Errorf("reader error: %v", err)
			return
		}
		line = strings.TrimSuffix(line, "\n")
		select {
		case messageChan <- line:
		case <-done:
			return
		}
	}
	_ = dockerCmd.Wait()
}

func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error {
	if cmd.CheckIllegal(container, since, tail) {
		return buserr.New("ErrCmdIllegal")
	}
	ctx := c.Request.Context()
	commandArg := []string{"logs", container}
	dockerCommand := global.CONF.DockerConfig.Command
	if containerType == "compose" {
		var yamlFiles []string
		for _, item := range strings.Split(container, ",") {
			if len(item) != 0 {
				yamlFiles = append(yamlFiles, "-f", item)
			}
		}
		if dockerCommand == "docker-compose" {
			commandArg = append(yamlFiles, "logs")
		} else {
			commandArg = append(append([]string{"compose"}, yamlFiles...), "logs")
		}
	}

	if tail != "0" {
		commandArg = append(commandArg, "--tail")
		commandArg = append(commandArg, tail)
	}
	if since != "all" {
		commandArg = append(commandArg, "--since")
		commandArg = append(commandArg, since)
	}
	if timestamp {
		commandArg = append(commandArg, "-t")
	}
	var dockerCmd *exec.Cmd
	if containerType == "compose" && dockerCommand == "docker-compose" {
		dockerCmd = exec.CommandContext(ctx, "docker-compose", commandArg...)
	} else {
		dockerCmd = exec.CommandContext(ctx, "docker", commandArg...)
	}
	dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	stdout, err := dockerCmd.StdoutPipe()
	if err != nil {
		return err
	}
	dockerCmd.Stderr = dockerCmd.Stdout
	if err := dockerCmd.Start(); err != nil {
		return err
	}
	done := make(chan struct{})
	go func() {
		select {
		case <-ctx.Done():
			killContainerLogProcess(dockerCmd)
		case <-done:
		}
	}()
	defer close(done)

	tempFile, err := os.CreateTemp("", "cmd_output_*.txt")
	if err != nil {
		killContainerLogProcess(dockerCmd)
		_ = dockerCmd.Wait()
		return err
	}
	defer tempFile.Close()
	defer func() {
		if err := os.Remove(tempFile.Name()); err != nil {
			global.LOG.Errorf("os.Remove() failed: %v", err)
		}
	}()
	copyErr := copyContainerLogOutput(tempFile, stdout)
	waitErr := dockerCmd.Wait()
	if copyErr != nil {
		return copyErr
	}
	if waitErr != nil {
		if ctx.Err() != nil {
			return ctx.Err()
		}
		return waitErr
	}
	if _, err := tempFile.Seek(0, io.SeekStart); err != nil {
		return err
	}
	info, _ := tempFile.Stat()

	c.Header("Content-Length", strconv.FormatInt(info.Size(), 10))
	c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
	http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), tempFile)
	return nil
}

func copyContainerLogOutput(dst io.Writer, src io.Reader) error {
	reader := bufio.NewReader(src)
	for {
		line, err := reader.ReadString('\n')
		if len(line) > 0 {
			cleanLine := containerLogAnsiRegex.ReplaceAllString(line, "")
			if _, writeErr := io.WriteString(dst, cleanLine); writeErr != nil {
				return writeErr
			}
		}
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
	}
}

func killContainerLogProcess(command *exec.Cmd) {
	if command == nil || command.Process == nil {
		return
	}
	if pgid, err := syscall.Getpgid(command.Process.Pid); err == nil {
		_ = syscall.Kill(-pgid, syscall.SIGKILL)
		return
	}
	_ = command.Process.Kill()
}

func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
	client, err := docker.NewDockerClient()
	if err != nil {
		return nil, err
	}
	defer client.Close()
	res, err := client.ContainerStats(context.TODO(), id, false)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var stats *container.StatsResponse
	if err := json.Unmarshal(body, &stats); err != nil {
		return nil, err
	}
	var data dto.ContainerStats
	data.CPUPercent = calculateCPUPercentUnix(stats)
	data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
	data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
	if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
		data.Cache = float64(cache) / 1024 / 1024
	}
	data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
	data.ShotTime = stats.Read
	return &data, nil
}

func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string {
	var users []string
	std, err := cmd.RunDockerExecWithStdout(20*time.Second, req.Name, "cat", "/etc/passwd")
	if err != nil {
		return users
	}
	lines := strings.Split(string(std), "\n")
	for _, line := range lines {
		if strings.Contains(line, ":") {
			users = append(users, strings.Split(line, ":")[0])
		}
	}
	return users
}

func (u *ContainerService) ListContainerFiles(req dto.ContainerFileReq) ([]dto.ContainerFileInfo, error) {
	if len(req.Path) == 0 {
		req.Path = "/"
	}
	cli, err := docker.NewDockerClient()
	if err != nil {
		return nil, err
	}
	defer cli.Close()

	ctx := context.Background()
	stat, err := cli.ContainerStatPath(ctx, req.ContainerID, req.Path)
	if err != nil {
		return nil, normalizeContainerFileError(err)
	}
	isDir := stat.Mode.IsDir()
	isLink := stat.Mode&os.ModeSymlink != 0
	if isLink && !isDir {
		linkDir, linkErr := isContainerDir(cli, req.ContainerID, req.Path)
		if linkErr == nil {
			isDir = linkDir
		}
	}
	if !isDir {
		return []dto.ContainerFileInfo{toContainerFileInfo(req.Path, stat, isDir)}, nil
	}

	output, err := runContainerCommand(cli, req.ContainerID, []string{"ls", "-1A", "--", req.Path})
	if err != nil {
		return nil, normalizeContainerFileError(err)
	}
	lines := strings.Split(strings.TrimSpace(output), "\n")
	files := make([]dto.ContainerFileInfo, 0, len(lines))
	for _, line := range lines {
		name := strings.TrimSpace(line)
		if len(name) == 0 || name == "." || name == ".." {
			continue
		}
		childPath := req.Path
		if childPath == "/" {
			childPath = "/" + name
		} else {
			childPath = strings.TrimSuffix(childPath, "/") + "/" + name
		}
		childStat, statErr := cli.ContainerStatPath(ctx, req.ContainerID, childPath)
		if statErr != nil {
			continue
		}
		childIsDir := childStat.Mode.IsDir()
		if childStat.Mode&os.ModeSymlink != 0 && !childIsDir {
			linkDir, linkErr := isContainerDir(cli, req.ContainerID, childPath)
			if linkErr == nil {
				childIsDir = linkDir
			}
		}
		files = append(files, toContainerFileInfo(childPath, childStat, childIsDir))
	}
	sort.Slice(files, func(i, j int) bool {
		if files[i].IsDir != files[j].IsDir {
			return files[i].IsDir
		}
		return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name)
	})
	return files, nil
}

func (u *ContainerService) DeleteContainerFile(req dto.ContainerFileBatchDeleteReq) error {
	for _, item := range req.Paths {
		if strings.TrimSpace(item) == "/" {
			return buserr.New("ErrPathNotDelete")
		}
	}
	cli, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	defer cli.Close()

	command := []string{"rm", "-rf", "--"}
	command = append(command, req.Paths...)
	_, err = runContainerCommand(cli, req.ContainerID, command)
	return err
}

func (u *ContainerService) UploadContainerFile(req dto.ContainerFileReq, fileName string, fileSize int64, file io.Reader) error {
	if len(req.Path) == 0 {
		req.Path = "/"
	}
	safeName := path.Base(fileName)
	if safeName == "." || safeName == "/" || len(safeName) == 0 {
		return buserr.New("ErrInvalidChar")
	}

	cli, err := docker.NewDockerClient()
	if err != nil {
		return err
	}
	defer cli.Close()

	ctx := context.Background()
	stat, err := cli.ContainerStatPath(ctx, req.ContainerID, req.Path)
	if err != nil {
		if _, mkErr := runContainerCommand(cli, req.ContainerID, []string{"mkdir", "-p", "--", req.Path}); mkErr != nil {
			return mkErr
		}
		stat, err = cli.ContainerStatPath(ctx, req.ContainerID, req.Path)
		if err != nil {
			return err
		}
	}
	if !stat.Mode.IsDir() {
		return fmt.Errorf("path %s is not directory", req.Path)
	}

	pipeReader, pipeWriter := io.Pipe()
	writeErr := make(chan error, 1)
	go func() {
		tw := tar.NewWriter(pipeWriter)
		header := &tar.Header{
			Name:    safeName,
			Mode:    0644,
			Size:    fileSize,
			ModTime: time.Now(),
		}
		if err := tw.WriteHeader(header); err != nil {
			_ = tw.Close()
			_ = pipeWriter.CloseWithError(err)
			writeErr <- err
			return
		}
		if _, err := io.Copy(tw, file); err != nil {
			_ = tw.Close()
			_ = pipeWriter.CloseWithError(err)
			writeErr <- err
			return
		}
		if err := tw.Close(); err != nil {
			_ = pipeWriter.CloseWithError(err)
			writeErr <- err
			return
		}
		_ = pipeWriter.Close()
		writeErr <- nil
	}()

	err = cli.CopyToContainer(ctx, req.ContainerID, req.Path, pipeReader, container.CopyToContainerOptions{
		CopyUIDGID: true,
	})
	if err != nil {
		_ = pipeReader.CloseWithError(err)
		_ = pipeWriter.CloseWithError(err)
		<-writeErr
		return err
	}
	if err := <-writeErr; err != nil {
		return err
	}
	return nil
}

func (u *ContainerService) GetContainerFileContent(req dto.ContainerFileReq) (*dto.ContainerFileContent, error) {
	if len(req.Path) == 0 {
		return nil, buserr.New("ErrInvalidChar")
	}
	cli, err := docker.NewDockerClient()
	if err != nil {
		return nil, err
	}
	defer cli.Close()

	stat, err := cli.ContainerStatPath(context.Background(), req.ContainerID, req.Path)
	if err != nil {
		return nil, normalizeContainerFileError(err)
	}
	if stat.Mode.IsDir() {
		return nil, fmt.Errorf("path %s is directory", req.Path)
	}

	content := &dto.ContainerFileContent{Size: stat.Size}
	headBytes, err := runContainerCommandRaw(cli, req.ContainerID, []string{"head", "-c", "4096", "--", req.Path})
	if err != nil {
		return nil, err
	}
	if bytes.IndexByte(headBytes, 0) >= 0 {
		content.IsBinary = true
		return content, nil
	}

	const inlinePreviewMax = 512 * 1024
	if stat.Size <= inlinePreviewMax {
		raw, err := runContainerCommandRaw(cli, req.ContainerID, []string{"cat", "--", req.Path})
		if err != nil {
			return nil, err
		}
		content.Content = string(raw)
		return content, nil
	}

	raw, err := runContainerCommandRaw(cli, req.ContainerID, []string{"tail", "-n", "300", "--", req.Path})
	if err != nil {
		return nil, err
	}
	content.Content = string(raw)
	content.Truncated = true
	return content, nil
}

func (u *ContainerService) GetContainerFileSize(req dto.ContainerFileReq) (int64, error) {
	if len(req.Path) == 0 {
		return 0, buserr.New("ErrInvalidChar")
	}
	cli, err := docker.NewDockerClient()
	if err != nil {
		return 0, err
	}
	defer cli.Close()

	stat, err := cli.ContainerStatPath(context.Background(), req.ContainerID, req.Path)
	if err != nil {
		return 0, normalizeContainerFileError(err)
	}
	if !stat.Mode.IsDir() {
		return stat.Size, nil
	}
	output, err := runContainerCommand(cli, req.ContainerID, []string{"du", "-sb", "--", req.Path})
	if err != nil {
		return 0, err
	}
	parts := strings.Fields(output)
	if len(parts) == 0 {
		return 0, fmt.Errorf("invalid du output")
	}
	size, err := strconv.ParseInt(parts[0], 10, 64)
	if err != nil {
		return 0, err
	}
	return size, nil
}

func (u *ContainerService) DownloadContainerFile(req dto.ContainerFileReq) (io.ReadCloser, string, string, error) {
	if len(req.Path) == 0 {
		req.Path = "/"
	}
	cli, err := docker.NewDockerClient()
	if err != nil {
		return nil, "", "", err
	}

	ctx := context.Background()
	stat, err := cli.ContainerStatPath(ctx, req.ContainerID, req.Path)
	if err != nil {
		_ = cli.Close()
		return nil, "", "", normalizeContainerFileError(err)
	}

	fileName := stat.Name
	if len(fileName) == 0 {
		fileName = "container-file"
	}
	if stat.Mode.IsDir() {
		if _, err := runContainerCommand(cli, req.ContainerID, []string{"tar", "--help"}); err != nil {
			_ = cli.Close()
			return nil, "", "", fmt.Errorf("tar command not found in container")
		}

		targetPath := path.Clean(req.Path)
		parentPath := path.Dir(targetPath)
		targetName := path.Base(targetPath)
		if parentPath == "." || parentPath == "" {
			parentPath = "/"
		}
		tarStream, err := runContainerCommandStream(cli, req.ContainerID, []string{
			"tar", "-czf", "-", "-C", parentPath, "--", targetName,
		})
		if err != nil {
			_ = cli.Close()
			return nil, "", "", err
		}
		if !strings.HasSuffix(fileName, ".tar.gz") {
			fileName += ".tar.gz"
		}
		return &closeHookReader{
			ReadCloser: tarStream,
			onClose:    cli.Close,
		}, fileName, "application/gzip", nil
	}

	fileStream, err := runContainerCommandStream(cli, req.ContainerID, []string{"cat", "--", req.Path})
	if err != nil {
		_ = cli.Close()
		return nil, "", "", err
	}
	return &closeHookReader{
		ReadCloser: fileStream,
		onClose:    cli.Close,
	}, fileName, "application/octet-stream", nil
}

func normalizeContainerFileError(err error) error {
	if err == nil {
		return nil
	}
	message := strings.ToLower(err.Error())
	if strings.Contains(message, "no such file or directory") || strings.Contains(message, "not found") {
		return buserr.New("ErrPathNotFound")
	}
	return err
}

func runContainerCommand(cli *client.Client, containerID string, command []string) (string, error) {
	raw, err := runContainerCommandRaw(cli, containerID, command)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(raw)), nil
}

type closeHookReader struct {
	io.ReadCloser
	onClose func() error
}

func (r *closeHookReader) Close() error {
	var closeErr error
	if r.ReadCloser != nil {
		closeErr = r.ReadCloser.Close()
	}
	if r.onClose != nil {
		if err := r.onClose(); err != nil && closeErr == nil {
			closeErr = err
		}
	}
	return closeErr
}

func runContainerCommandRaw(cli *client.Client, containerID string, command []string) ([]byte, error) {
	ctx := context.Background()
	resp, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
		Cmd:          command,
		AttachStdout: true,
		AttachStderr: true,
	})
	if err != nil {
		return nil, err
	}
	hijack, err := cli.ContainerExecAttach(ctx, resp.ID, container.ExecAttachOptions{})
	if err != nil {
		return nil, err
	}
	defer hijack.Close()

	raw, err := io.ReadAll(hijack.Reader)
	if err != nil {
		return nil, err
	}
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	if _, err := stdcopy.StdCopy(&stdout, &stderr, bytes.NewReader(raw)); err != nil {
		return nil, err
	}
	output := strings.TrimSpace(stdout.String())
	errorOutput := strings.TrimSpace(stderr.String())
	info, err := cli.ContainerExecInspect(ctx, resp.ID)
	if err != nil {
		return nil, err
	}
	if info.ExitCode != 0 {
		if len(errorOutput) != 0 {
			return nil, fmt.Errorf("%s", errorOutput)
		}
		if len(output) == 0 {
			return nil, fmt.Errorf("container command failed with exit code %d", info.ExitCode)
		}
		return nil, fmt.Errorf("%s", output)
	}
	return stdout.Bytes(), nil
}

func runContainerCommandStream(cli *client.Client, containerID string, command []string) (io.ReadCloser, error) {
	ctx := context.Background()
	resp, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
		Cmd:          command,
		AttachStdout: true,
		AttachStderr: true,
	})
	if err != nil {
		return nil, err
	}
	hijack, err := cli.ContainerExecAttach(ctx, resp.ID, container.ExecAttachOptions{})
	if err != nil {
		return nil, err
	}

	pipeReader, pipeWriter := io.Pipe()
	go func() {
		defer hijack.Close()
		var stderr bytes.Buffer
		_, copyErr := stdcopy.StdCopy(pipeWriter, &stderr, hijack.Reader)
		if copyErr != nil {
			_ = pipeWriter.CloseWithError(copyErr)
			return
		}
		info, inspectErr := cli.ContainerExecInspect(ctx, resp.ID)
		if inspectErr != nil {
			_ = pipeWriter.CloseWithError(inspectErr)
			return
		}
		if info.ExitCode != 0 {
			msg := strings.TrimSpace(stderr.String())
			if len(msg) == 0 {
				msg = fmt.Sprintf("container command failed with exit code %d", info.ExitCode)
			}
			_ = pipeWriter.CloseWithError(fmt.Errorf("%s", msg))
			return
		}
		_ = pipeWriter.Close()
	}()
	return pipeReader, nil
}

func toContainerFileInfo(filePath string, stat container.PathStat, isDir bool) dto.ContainerFileInfo {
	name := stat.Name
	if len(name) == 0 {
		items := strings.Split(strings.TrimSuffix(filePath, "/"), "/")
		name = items[len(items)-1]
	}
	isLink := stat.Mode&os.ModeSymlink != 0
	return dto.ContainerFileInfo{
		Name:    name,
		Path:    filePath,
		IsDir:   isDir,
		IsLink:  isLink,
		LinkTo:  stat.LinkTarget,
		Size:    stat.Size,
		Mode:    stat.Mode.String(),
		ModTime: stat.Mtime.Format(constant.DateTimeLayout),
	}
}

func isContainerDir(cli *client.Client, containerID, targetPath string) (bool, error) {
	checkPath := strings.TrimSuffix(targetPath, "/") + "/."
	_, err := runContainerCommand(cli, containerID, []string{"ls", "-d", "--", checkPath})
	if err != nil {
		return false, err
	}
	return true, nil
}

func stringsToMap(list []string) map[string]string {
	var labelMap = make(map[string]string)
	for _, label := range list {
		if strings.Contains(label, "=") {
			sps := strings.SplitN(label, "=", 2)
			labelMap[sps[0]] = sps[1]
		}
	}
	return labelMap
}
func stringsToMap2(list []string) map[string]*string {
	var labelMap = make(map[string]*string)
	for _, label := range list {
		if strings.Contains(label, "=") {
			sps := strings.SplitN(label, "=", 2)
			labelMap[sps[0]] = &sps[1]
		}
	}
	return labelMap
}

func calculateCPUPercentUnix(stats *container.StatsResponse) float64 {
	cpuPercent := 0.0
	cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
	systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)

	if systemDelta > 0.0 && cpuDelta > 0.0 {
		cpuPercent = (cpuDelta / systemDelta) * 100.0
		if len(stats.CPUStats.CPUUsage.PercpuUsage) != 0 {
			cpuPercent = cpuPercent * float64(len(stats.CPUStats.CPUUsage.PercpuUsage))
		}
	}
	return cpuPercent
}
func calculateMemPercentUnix(memStats container.MemoryStats) float64 {
	memPercent := 0.0
	memUsage := calculateMemUsageUnixNoCache(memStats)
	memLimit := float64(memStats.Limit)
	if memUsage > 0.0 && memLimit > 0.0 {
		memPercent = (float64(memUsage) / float64(memLimit)) * 100.0
	}
	return memPercent
}
func calculateMemUsageUnixNoCache(mem container.MemoryStats) uint64 {
	if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
		return mem.Usage - v
	}
	if v := mem.Stats["inactive_file"]; v < mem.Usage {
		return mem.Usage - v
	}
	return mem.Usage
}
func calculateBlockIO(blkio container.BlkioStats) (blkRead float64, blkWrite float64) {
	for _, bioEntry := range blkio.IoServiceBytesRecursive {
		switch strings.ToLower(bioEntry.Op) {
		case "read":
			blkRead = (blkRead + float64(bioEntry.Value)) / 1024 / 1024
		case "write":
			blkWrite = (blkWrite + float64(bioEntry.Value)) / 1024 / 1024
		}
	}
	return
}
func calculateNetwork(network map[string]container.NetworkStats) (float64, float64) {
	var rx, tx float64

	for _, v := range network {
		rx += float64(v.RxBytes) / 1024
		tx += float64(v.TxBytes) / 1024
	}
	return rx, tx
}

func checkImageExist(client *client.Client, imageItem string) bool {
	if client == nil {
		var err error
		client, err = docker.NewDockerClient()
		if err != nil {
			return false
		}
	}
	images, err := client.ImageList(context.Background(), image.ListOptions{})
	if err != nil {
		return false
	}

	for _, img := range images {
		for _, tag := range img.RepoTags {
			if tag == imageItem || tag == imageItem+":latest" {
				return true
			}
		}
	}
	return false
}

func checkImageLike(client *client.Client, imageName string) bool {
	if client == nil {
		var err error
		client, err = docker.NewDockerClient()
		if err != nil {
			return false
		}
	}
	images, err := client.ImageList(context.Background(), image.ListOptions{})
	if err != nil {
		return false
	}

	for _, img := range images {
		for _, tag := range img.RepoTags {
			parts := strings.Split(tag, "/")
			imageNameWithTag := parts[len(parts)-1]

			if imageNameWithTag == imageName {
				return true
			}
		}
	}
	return false
}

func pullImages(task *task.Task, client *client.Client, imageName string) error {
	dockerCli := docker.NewClientWithExist(client)
	options := image.PullOptions{}
	repos, _ := imageRepoRepo.List()
	if len(repos) != 0 {
		for _, repo := range repos {
			if strings.HasPrefix(imageName, repo.DownloadUrl) && repo.Auth {
				authConfig := registry.AuthConfig{
					Username: repo.Username,
					Password: repo.Password,
				}
				encodedJSON, err := json.Marshal(authConfig)
				if err != nil {
					return err
				}
				authStr := base64.URLEncoding.EncodeToString(encodedJSON)
				options.RegistryAuth = authStr
			}
		}
	} else {
		hasAuth, authStr := loadAuthInfo(imageName)
		if hasAuth {
			options.RegistryAuth = authStr
		}
	}
	return dockerCli.PullImageWithProcessAndOptions(task, imageName, options)
}

func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
	data := dto.ContainerListStats{
		ContainerID: containerItem,
	}
	res, err := client.ContainerStats(context.Background(), containerItem, false)
	if err != nil {
		return data
	}

	defer res.Body.Close()
	body, err := io.ReadAll(res.Body)
	if err != nil {
		return data
	}
	var stats *container.StatsResponse
	if err := json.Unmarshal(body, &stats); err != nil {
		return data
	}

	data.CPUTotalUsage = stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage
	data.SystemUsage = stats.CPUStats.SystemUsage - stats.PreCPUStats.SystemUsage
	data.CPUPercent = calculateCPUPercentUnix(stats)
	data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)

	data.MemoryCache = stats.MemoryStats.Stats["cache"]
	data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats)
	data.MemoryLimit = stats.MemoryStats.Limit

	data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
	return data
}

func checkPortStats(ports []dto.PortHelper, checkInUse bool) (nat.PortMap, error) {
	portMap := make(nat.PortMap)
	if len(ports) == 0 {
		return portMap, nil
	}
	for _, port := range ports {
		if strings.Contains(port.ContainerPort, "-") {
			if !strings.Contains(port.HostPort, "-") {
				return portMap, buserr.New("ErrPortRules")
			}
			hostStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0])
			hostEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1])
			containerStart, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[0])
			containerEnd, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[1])
			if (hostEnd-hostStart) <= 0 || (containerEnd-containerStart) <= 0 {
				return portMap, buserr.New("ErrPortRules")
			}
			if (containerEnd - containerStart) != (hostEnd - hostStart) {
				return portMap, buserr.New("ErrPortRules")
			}
			for i := 0; i <= hostEnd-hostStart; i++ {
				bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP}
				portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
			}
			for i := hostStart; i <= hostEnd; i++ {
				if checkInUse && common.ScanPortWithIP(port.HostIP, i) {
					return portMap, buserr.WithDetail("ErrPortInUsed", i, nil)
				}
			}
		} else {
			portItem := 0
			if strings.Contains(port.HostPort, "-") {
				portItem, _ = strconv.Atoi(strings.Split(port.HostPort, "-")[0])
			} else {
				portItem, _ = strconv.Atoi(port.HostPort)
			}
			if checkInUse && common.ScanPortWithIP(port.HostIP, portItem) {
				return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
			}
			bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
			portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem}
		}
	}
	return portMap, nil
}

func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *container.InspectResponse) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
	var config container.Config
	var hostConf container.HostConfig
	if !isCreate {
		config = *oldContainer.Config
		hostConf = *oldContainer.HostConfig
	}
	var networkConf network.NetworkingConfig

	portMap, err := checkPortStats(req.ExposedPorts, isCreate)
	if err != nil {
		return nil, nil, nil, err
	}
	exposed := make(nat.PortSet)
	for port := range portMap {
		exposed[port] = struct{}{}
	}
	config.Image = req.Image
	config.Cmd = req.Cmd
	config.Entrypoint = req.Entrypoint
	config.Env = req.Env
	config.Labels = stringsToMap(req.Labels)
	config.ExposedPorts = exposed
	config.OpenStdin = req.OpenStdin
	config.Tty = req.Tty
	config.Hostname = req.Hostname
	config.Domainname = req.DomainName
	config.User = req.User
	config.WorkingDir = req.WorkingDir

	if len(req.Networks) != 0 {
		networkConf.EndpointsConfig = make(map[string]*network.EndpointSettings)
		for _, item := range req.Networks {
			switch item.Network {
			case "host", "none", "bridge":
				hostConf.NetworkMode = container.NetworkMode(item.Network)
			}
			endpoint := &network.EndpointSettings{
				Links:      append([]string(nil), item.Links...),
				Aliases:    append([]string(nil), item.Aliases...),
				DriverOpts: cloneStringMap(item.DriverOpts),
				GwPriority: item.GwPriority,
				MacAddress: item.MacAddr,
			}
			if item.Ipv4 != "" || item.Ipv6 != "" || len(item.LinkLocalIPs) != 0 {
				endpoint.IPAMConfig = &network.EndpointIPAMConfig{
					IPv4Address:  item.Ipv4,
					IPv6Address:  item.Ipv6,
					LinkLocalIPs: append([]string(nil), item.LinkLocalIPs...),
				}
			}
			networkConf.EndpointsConfig[item.Network] = endpoint
		}
	} else {
		return nil, nil, nil, fmt.Errorf("please set up the network")
	}

	hostConf.Privileged = req.Privileged
	hostConf.AutoRemove = req.AutoRemove
	hostConf.CPUShares = req.CPUShares
	hostConf.PublishAllPorts = req.PublishAllPorts
	hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
	if req.RestartPolicy == "on-failure" {
		hostConf.RestartPolicy.MaximumRetryCount = 5
	}
	hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000)
	hostConf.Memory = int64(req.Memory * 1024 * 1024)
	hostConf.MemorySwap = 0
	hostConf.PortBindings = portMap
	hostConf.Binds = []string{}
	hostConf.Mounts = []mount.Mount{}
	hostConf.DNS = req.DNS
	hostConf.ExtraHosts = []string{}
	for _, item := range req.ExtraHosts {
		if len(item.Hostname) == 0 || len(item.IP) == 0 {
			continue
		}
		hostConf.ExtraHosts = append(hostConf.ExtraHosts, fmt.Sprintf("%s:%s", item.Hostname, item.IP))
	}
	config.Volumes = make(map[string]struct{})
	for _, volume := range req.Volumes {
		item := mount.Mount{
			Type:     mount.Type(volume.Type),
			Source:   volume.SourceDir,
			Target:   volume.ContainerDir,
			ReadOnly: volume.Mode == "ro",
		}
		if volume.Type == "bind" {
			item.BindOptions = &mount.BindOptions{
				Propagation: mount.Propagation(volume.Shared),
			}
		}
		hostConf.Mounts = append(hostConf.Mounts, item)
		config.Volumes[volume.ContainerDir] = struct{}{}
	}
	return &config, &hostConf, &networkConf, nil
}

func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper {
	var datas []dto.VolumeHelper
	for _, bind := range binds {
		var volumeItem dto.VolumeHelper
		volumeItem.Type = string(bind.Type)
		if bind.Type == "volume" {
			volumeItem.SourceDir = bind.Name
		} else {
			volumeItem.SourceDir = bind.Source
		}
		volumeItem.ContainerDir = bind.Destination
		volumeItem.Mode = "ro"
		if bind.RW {
			volumeItem.Mode = "rw"
		}
		volumeItem.Shared = string(bind.Propagation)
		datas = append(datas, volumeItem)
	}
	return datas
}

func loadPortByInspect(id string, client *client.Client) ([]container.Port, error) {
	containerItem, err := client.ContainerInspect(context.Background(), id)
	if err != nil {
		return nil, err
	}
	var itemPorts []container.Port
	for key, val := range containerItem.ContainerJSONBase.HostConfig.PortBindings {
		if !strings.Contains(string(key), "/") {
			continue
		}
		item := strings.Split(string(key), "/")
		itemPort, _ := strconv.ParseUint(item[0], 10, 16)

		for _, itemVal := range val {
			publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16)
			itemPorts = append(itemPorts, container.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
		}
	}
	return itemPorts, nil
}
func transPortToStr(ports []container.Port) []string {
	var (
		ipv4Ports []container.Port
		ipv6Ports []container.Port
	)
	for _, port := range ports {
		if strings.Contains(port.IP, ":") {
			ipv6Ports = append(ipv6Ports, port)
		} else {
			ipv4Ports = append(ipv4Ports, port)
		}
	}
	list1 := simplifyPort(ipv4Ports)
	list2 := simplifyPort(ipv6Ports)
	return append(list1, list2...)
}
func simplifyPort(ports []container.Port) []string {
	var datas []string
	if len(ports) == 0 {
		return datas
	}
	if len(ports) == 1 {
		ip := ""
		if len(ports[0].IP) != 0 {
			ip = ports[0].IP + ":"
		}
		itemPortStr := fmt.Sprintf("%s%v/%s", ip, ports[0].PrivatePort, ports[0].Type)
		if ports[0].PublicPort != 0 {
			itemPortStr = fmt.Sprintf("%s%v->%v/%s", ip, ports[0].PublicPort, ports[0].PrivatePort, ports[0].Type)
		}
		datas = append(datas, itemPortStr)
		return datas
	}

	sort.Slice(ports, func(i, j int) bool {
		return ports[i].PrivatePort < ports[j].PrivatePort
	})
	start := ports[0]

	for i := 1; i < len(ports); i++ {
		if ports[i].PrivatePort != ports[i-1].PrivatePort+1 || ports[i].IP != ports[i-1].IP || ports[i].PublicPort != ports[i-1].PublicPort+1 || ports[i].Type != ports[i-1].Type {
			if ports[i-1].PrivatePort == start.PrivatePort {
				itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
				if start.PublicPort != 0 {
					itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
				}
				if len(start.IP) == 0 {
					itemPortStr = strings.TrimPrefix(itemPortStr, ":")
				}
				datas = append(datas, itemPortStr)
			} else {
				itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
				if start.PublicPort != 0 {
					itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i-1].PublicPort, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
				}
				if len(start.IP) == 0 {
					itemPortStr = strings.TrimPrefix(itemPortStr, ":")
				}
				datas = append(datas, itemPortStr)
			}
			start = ports[i]
		}
		if i == len(ports)-1 {
			if ports[i].PrivatePort == start.PrivatePort {
				itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
				if start.PublicPort != 0 {
					itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
				}
				if len(start.IP) == 0 {
					itemPortStr = strings.TrimPrefix(itemPortStr, ":")
				}
				datas = append(datas, itemPortStr)
			} else {
				itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i].PrivatePort, start.Type)
				if start.PublicPort != 0 {
					itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i].PublicPort, start.PrivatePort, ports[i].PrivatePort, start.Type)
				}
				if len(start.IP) == 0 {
					itemPortStr = strings.TrimPrefix(itemPortStr, ":")
				}
				datas = append(datas, itemPortStr)
			}
		}
	}
	return datas
}

func loadComposeCount(client *client.Client) int {
	options := container.ListOptions{All: true}
	options.Filters = filters.NewArgs()
	options.Filters.Add("label", composeProjectLabel)
	list, err := client.ContainerList(context.Background(), options)
	if err != nil {
		return 0
	}
	composeCreatedByLocal, _ := composeRepo.ListRecord()
	composeMap := make(map[string]struct{})
	for _, container := range list {
		if name, ok := container.Labels[composeProjectLabel]; ok {
			if _, has := composeMap[name]; !has {
				composeMap[name] = struct{}{}
			}
		}
	}
	for _, compose := range composeCreatedByLocal {
		if len(compose.Path) == 0 {
			continue
		}
		if _, has := composeMap[compose.Name]; !has {
			composeMap[compose.Name] = struct{}{}
		}
	}

	return len(composeMap)
}
func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
	var exposedPorts []dto.PortHelper
	samePortMap := make(map[string]dto.PortHelper)
	ports := transPortToStr(itemPorts)
	for _, item := range ports {
		itemStr := strings.Split(item, "->")
		if len(itemStr) < 2 {
			continue
		}
		var itemPort dto.PortHelper
		lastIndex := strings.LastIndex(itemStr[0], ":")
		if lastIndex == -1 {
			itemPort.HostPort = itemStr[0]
		} else {
			itemPort.HostIP = itemStr[0][0:lastIndex]
			itemPort.HostPort = itemStr[0][lastIndex+1:]
		}
		itemContainer := strings.Split(itemStr[1], "/")
		if len(itemContainer) != 2 {
			continue
		}
		itemPort.ContainerPort = itemContainer[0]
		itemPort.Protocol = itemContainer[1]
		keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
		if val, ok := samePortMap[keyItem]; ok {
			val.HostIP = ""
			samePortMap[keyItem] = val
		} else {
			samePortMap[keyItem] = itemPort
		}
	}
	for _, val := range samePortMap {
		exposedPorts = append(exposedPorts, val)
	}
	return exposedPorts
}

func searchWithFilter(req dto.PageContainer, containers []container.Summary) []dto.ContainerInfo {
	var (
		records []dto.ContainerInfo
		list    []container.Summary
	)

	if req.ExcludeAppStore {
		for _, item := range containers {
			if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
				continue
			}
			list = append(list, item)
		}
	} else {
		list = containers
	}

	if len(req.Name) != 0 {
		length, count := len(list), 0
		for count < length {
			if !strings.Contains(list[count].Names[0][1:], req.Name) && !strings.Contains(list[count].Image, req.Name) {
				list = append(list[:count], list[(count+1):]...)
				length--
			} else {
				count++
			}
		}
	}
	if req.State != "all" {
		length, count := len(list), 0
		for count < length {
			if list[count].State != req.State {
				list = append(list[:count], list[(count+1):]...)
				length--
			} else {
				count++
			}
		}
	}
	switch req.OrderBy {
	case "name":
		sort.Slice(list, func(i, j int) bool {
			if req.Order == constant.OrderAsc {
				return list[i].Names[0][1:] < list[j].Names[0][1:]
			}
			return list[i].Names[0][1:] > list[j].Names[0][1:]
		})
	default:
		sort.Slice(list, func(i, j int) bool {
			if req.Order == constant.OrderAsc {
				return list[i].Created < list[j].Created
			}
			return list[i].Created > list[j].Created
		})
	}
	for _, item := range list {
		IsFromCompose := false
		if _, ok := item.Labels[composeProjectLabel]; ok {
			IsFromCompose = true
		}
		IsFromApp := false
		if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
			IsFromApp = true
		}
		exposePorts := transPortToStr(item.Ports)
		info := dto.ContainerInfo{
			ContainerID:   item.ID,
			CreateTime:    time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
			Name:          item.Names[0][1:],
			Ports:         exposePorts,
			ImageId:       strings.Split(item.ImageID, ":")[1],
			ImageName:     item.Image,
			State:         item.State,
			RunTime:       item.Status,
			IsFromApp:     IsFromApp,
			IsFromCompose: IsFromCompose,
		}
		if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 {
			networks := make([]string, 0, len(item.NetworkSettings.Networks))
			for key := range item.NetworkSettings.Networks {
				networks = append(networks, item.NetworkSettings.Networks[key].IPAddress)
			}
			sort.Strings(networks)
			info.Network = networks
		}
		records = append(records, info)
	}
	descriptions, _ := settingRepo.GetDescriptionList(repo.WithByType("container"))
	for i := 0; i < len(records); i++ {
		for _, desc := range descriptions {
			if desc.ID == records[i].ContainerID {
				records[i].Description = desc.Description
				records[i].IsPinned = desc.IsPinned
			}
		}
	}
	sort.Slice(records, func(i, j int) bool {
		return records[i].IsPinned && !records[j].IsPinned
	})
	return records
}
