package core

import (
	"archive/tar"
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"intent-system/pkg/config"
	"intent-system/pkg/dal/dao"
	"intent-system/pkg/dal/models"
	"intent-system/pkg/itypes"
	"intent-system/pkg/proto"
	"intent-system/pkg/utils"
	"io"
	"net"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/civet148/log"
	"github.com/civet148/sqlca/v2"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"

	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/daemon"
)

type DeployCore struct {
	db  *sqlca.Engine
	cfg *config.Config

	deployDAO     *dao.DeployDAO
	tagDAO        *dao.TagDAO
	userDAO       *dao.UserDAO
	newsDAO       *dao.NewsDAO
	newsDraftDAO  *dao.NewsDraftDAO
	customerDAO   *dao.CustomerDAO
	dictionaryDAO *dao.DictionaryDAO
	subDAO        *dao.NewsSubscribeDAO
	qaDAO         *dao.QuestionAnswerDAO
	qaDraftDAO    *dao.QuestionDraftDAO
}

type progressReader struct {
	r         io.Reader
	readBytes int64
	total     int64
	update    func(p int) // p 是 0-100 的百分比
	lastPct   int
}

func GetSupabaseURL() string { // ← 仅首字母大写即可被其他包调用
	return getSupabaseURL()
}

func getSupabaseURL() string {
	// 获取默认网关 IP
	gatewayIP, err := getDefaultGateway()
	if err != nil {
		fmt.Println("⚠️ 获取默认网关失败，使用 fallback:", err)
		return "http://localhost:8000"
	}

	// 获取所有本地网卡 IP 和子网
	ifaces, err := net.Interfaces()
	if err != nil {
		fmt.Println("⚠️ 获取本地接口失败:", err)
		return "http://localhost:8000"
	}

	for _, iface := range ifaces {
		if (iface.Flags&net.FlagUp == 0) || (iface.Flags&net.FlagLoopback != 0) {
			continue // 跳过不活跃或loopback接口
		}

		addrs, err := iface.Addrs()
		if err != nil {
			continue
		}

		for _, addr := range addrs {
			ipNet, ok := addr.(*net.IPNet)
			if !ok || ipNet.IP.To4() == nil {
				continue
			}

			// 检查网关是否在这个接口的子网中
			if ipNet.Contains(gatewayIP) {
				// 找到同网段的IP
				return fmt.Sprintf("http://%s:8000", ipNet.IP.String())
			}
		}
	}

	// 如果没有匹配的网段，使用 fallback
	return "http://localhost:8000"
}

func getDefaultGateway() (net.IP, error) {
	// 调用 Linux 命令获取默认路由
	cmd := exec.Command("ip", "route", "show", "default")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return nil, err
	}

	lines := strings.Split(out.String(), "\n")
	for _, line := range lines {
		if strings.HasPrefix(line, "default") {
			fields := strings.Fields(line)
			for i, f := range fields {
				if f == "via" && i+1 < len(fields) {
					return net.ParseIP(fields[i+1]), nil
				}
			}
		}
	}

	return nil, fmt.Errorf("default gateway not found")
}

func (pr *progressReader) Read(p []byte) (int, error) {
	n, err := pr.r.Read(p)
	pr.readBytes += int64(n)

	percent := int(float64(pr.readBytes) / float64(pr.total) * 100)
	if percent > pr.lastPct {
		pr.lastPct = percent
		pr.update(percent)
	}

	return n, err
}

func NewDeployCore(cfg *config.Config, db *sqlca.Engine) *DeployCore {
	return &DeployCore{
		db:            db,
		cfg:           cfg,
		tagDAO:        dao.NewTagDAO(db),
		userDAO:       dao.NewUserDAO(db),
		newsDAO:       dao.NewNewsDAO(db),
		newsDraftDAO:  dao.NewNewsDraftDAO(db),
		deployDAO:     dao.NewDeployDAO(db),
		customerDAO:   dao.NewCustomerDAO(db),
		dictionaryDAO: dao.NewDictionaryDAO(db),
		qaDAO:         dao.NewQuestionAnswerDAO(db),
		subDAO:        dao.NewNewsSubscribeDAO(db),
		qaDraftDAO:    dao.NewQuestionDraftDAO(db),
	}
}

type manifestItem struct {
	RepoTags []string `json:"RepoTags"`
	Config   string   `json:"Config"`
}

// GetRunningContainerImages 获取正在运行的容器的镜像名称
func GetRunningContainerImages(cli *client.Client) ([]string, error) {
	containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
	if err != nil {
		return nil, err
	}

	var images []string
	for _, container := range containers {
		images = append(images, container.Image)
	}

	return images, nil
}

// GetOCIDigest 获取指定镜像的 OCI Digest
func GetOCIDigest(imageName string) (string, error) {
	// 解析镜像名称
	ref, err := name.ParseReference(imageName)
	if err != nil {
		return "", err
	}

	// 从 Docker Daemon 获取镜像
	img, err := daemon.Image(ref)
	if err != nil {
		return "", err
	}

	// 计算并返回 OCI Digest
	digest, err := img.Digest()
	if err != nil {
		return "", err
	}

	return digest.String(), nil
}

// GetAllRunningContainersOCIDigests 获取所有正在运行容器的镜像及其 OCI Digest
func GetAllRunningContainersOCIDigests(cli *client.Client) (map[string]string, error) {
	images, err := GetRunningContainerImages(cli)
	if err != nil {
		return nil, err
	}

	// 按顺序计算每个镜像的 OCI Digest
	digests := make(map[string]string)

	// 计算每个镜像的 OCI Digest（顺序执行）
	for _, image := range images {
		log.Printf("Getting OCI Digest for image: %s", image)
		digest, err := GetOCIDigest(image)
		if err != nil {
			log.Printf("Error calculating OCI Digest for image %s: %v", image, err)
			continue
		}
		// 存储结果
		digests[image] = digest
	}

	return digests, nil
}

// 从镜像 tar 文件中解析镜像名（tag）
func extractImageNameFromTar(tarPath string) (string, error) {
	file, err := os.Open(tarPath)
	if err != nil {
		return "", fmt.Errorf("open tar failed: %w", err)
	}
	defer file.Close()

	tarReader := tar.NewReader(file)
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return "", fmt.Errorf("read tar error: %w", err)
		}
		if header.Name == "manifest.json" {
			var manifests []manifestItem
			decoder := json.NewDecoder(tarReader)
			if err := decoder.Decode(&manifests); err != nil {
				return "", fmt.Errorf("decode manifest.json error: %w", err)
			}
			if len(manifests) == 0 || len(manifests[0].RepoTags) == 0 {
				return "", fmt.Errorf("no RepoTags found in manifest")
			}
			return manifests[0].RepoTags[0], nil
		}
	}
	return "", fmt.Errorf("manifest.json not found")
}

// 主函数：从 tar 文件部署容器
func DeployFromTar(ctx context.Context, tarPath string) (string, error) {
	log.Infof("Loading Docker image from tar: %s", tarPath)

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return "", fmt.Errorf("create docker client error: %w", err)
	}

	// 加载镜像 tar 到 docker
	tarFile, err := os.Open(tarPath)
	if err != nil {
		return "", fmt.Errorf("open tar error: %w", err)
	}
	defer tarFile.Close()

	loadResp, err := cli.ImageLoad(ctx, tarFile, true)
	if err != nil {
		return "", fmt.Errorf("image load error: %w", err)
	}
	defer loadResp.Body.Close()

	// 解析镜像名
	imageName, err := extractImageNameFromTar(tarPath)
	if err != nil {
		return "", fmt.Errorf("extract image name failed: %w", err)
	}
	log.Infof("Loaded image: %s", imageName)

	// 获取暴露端口
	imageInspect, _, err := cli.ImageInspectWithRaw(ctx, imageName)
	if err != nil {
		return "", fmt.Errorf("inspect image failed: %w", err)
	}
	if len(imageInspect.Config.ExposedPorts) == 0 {
		return "", fmt.Errorf("image has no exposed ports")
	}

	portBindings := nat.PortMap{}
	exposedPorts := nat.PortSet{}
	for port := range imageInspect.Config.ExposedPorts {
		hostPort := port.Port() // 同端口映射
		portBindings[port] = []nat.PortBinding{{
			HostIP:   "0.0.0.0",
			HostPort: hostPort,
		}}
		exposedPorts[port] = struct{}{}
		log.Infof("Exposing container port %s -> host port %s", port.Port(), hostPort)
	}

	// 使用镜像名作为容器名，但去掉冒号和标签部分
	containerName := imageName
	if strings.Contains(containerName, ":") {
		// 如果包含冒号（:），就去掉冒号后面的部分
		containerName = strings.SplitN(containerName, ":", 2)[0]
	}

	// 创建容器
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:        imageName,
		ExposedPorts: exposedPorts,
	}, &container.HostConfig{
		PortBindings: portBindings,
		RestartPolicy: container.RestartPolicy{
			Name: "unless-stopped",
		},
		Runtime: "nvidia",               // Enable GPU support (requires nvidia-docker runtime)
		ShmSize: 2 * 1024 * 1024 * 1024, // Set shared memory size to 2GB
	}, nil, nil, containerName)
	if err != nil {
		return "", fmt.Errorf("create container error: %w", err)
	}

	// 启动容器
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return "", fmt.Errorf("start container error: %w", err)
	}

	log.Infof("Container started successfully: %s", resp.ID)
	return resp.ID, nil
}

// DeployCradle loads /data/intent-system/cradle/prebuilt_models/cradle_amd64.tar,
// removes any existing cradle container & image, then redeploys and starts it.
func DeployCradleTar_old(ctx context.Context) (string, error) {
	const tarPath = "/data/intent-system/cradle/prebuilt_models/cradle_amd64.tar"

	// ── Docker client ───────────────────────────────────────────────
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return "", fmt.Errorf("create docker client error: %w", err)
	}
	defer cli.Close()

	// ── 解析镜像名 ───────────────────────────────────────────────────
	imageName, err := extractImageNameFromTar(tarPath)
	if err != nil {
		return "", fmt.Errorf("extract image name failed: %w", err)
	}
	containerName := strings.SplitN(imageName, ":", 2)[0]

	// ── 若存在旧容器则停止并删除 ───────────────────────────────────────
	{
		timeout := 10 * time.Second
		if err := cli.ContainerStop(ctx, containerName, &timeout); err == nil {
			log.Infof("stopped old container %s", containerName)
		}
		_ = cli.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{Force: true})
	}

	// ── 若存在旧镜像则删除 ───────────────────────────────────────────
	{
		_, _ = cli.ImageRemove(ctx, imageName, types.ImageRemoveOptions{
			Force:         true,
			PruneChildren: true,
		})
	}

	// ── 加载新镜像 ───────────────────────────────────────────────────
	tarFile, err := os.Open(tarPath)
	if err != nil {
		return "", fmt.Errorf("open tar error: %w", err)
	}
	defer tarFile.Close()

	loadResp, err := cli.ImageLoad(ctx, tarFile, true)
	if err != nil {
		return "", fmt.Errorf("image load error: %w", err)
	}
	_ = loadResp.Body.Close()
	log.Infof("loaded image %s", imageName)

	// ── 端口映射信息 ────────────────────────────────────────────────
	imageInspect, _, err := cli.ImageInspectWithRaw(ctx, imageName)
	if err != nil {
		return "", fmt.Errorf("inspect image failed: %w", err)
	}
	if len(imageInspect.Config.ExposedPorts) == 0 {
		return "", fmt.Errorf("image has no exposed ports")
	}
	portBindings := nat.PortMap{}
	exposedPorts := nat.PortSet{}
	for port := range imageInspect.Config.ExposedPorts {
		hostPort := port.Port()
		portBindings[port] = []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: hostPort}}
		exposedPorts[port] = struct{}{}
	}

	// ── 创建并启动容器 ───────────────────────────────────────────────
	supabaseURL := getSupabaseURL()
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:        imageName,
		ExposedPorts: exposedPorts,
		Env:          []string{fmt.Sprintf("SUPABASE_URL=%s", supabaseURL)},
	}, &container.HostConfig{
		PortBindings: portBindings,
		RestartPolicy: container.RestartPolicy{
			Name: "unless-stopped",
		},
		Runtime: "nvidia",
		ShmSize: 2 * 1024 * 1024 * 1024,
	}, nil, nil, containerName)
	if err != nil {
		return "", fmt.Errorf("create container error: %w", err)
	}

	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return "", fmt.Errorf("start container error: %w", err)
	}

	log.Infof("✅ cradle container started: %s", resp.ID)
	return resp.ID, nil
}

// DeployCradleTar 安全部署：探活通过才切换
func DeployCradleTar(ctx context.Context) (string, error) {
	const tarPath = "/data/intent-system/cradle/prebuilt_models/cradle_amd64.tar"

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return "", fmt.Errorf("create docker client: %w", err)
	}
	defer cli.Close()

	/* ---------- 0. 从 tar 预取镜像名 ---------- */
	imageName, err := extractImageNameFromTar(tarPath)
	if err != nil || imageName == "" {
		return "", fmt.Errorf("extract image name: %w", err)
	}
	if !strings.Contains(imageName, ":") {
		imageName += ":latest"
	}
	containerName := strings.SplitN(imageName, ":", 2)[0]
	log.Infof("[DEBUG] imageName=%s, containerName=%s", imageName, containerName)

	/* ---------- 1. 清理旧容器 ---------- */
	cs, _ := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
	for _, c := range cs {
		for _, n := range c.Names {
			if strings.TrimPrefix(n, "/") == containerName {
				timeout := 10 * time.Second
				_ = cli.ContainerStop(ctx, c.ID, &timeout)
				_ = cli.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true})
				break
			}
		}
	}

	/* ---------- 2. 清理旧镜像（忽略找不到的错误） ---------- */
	_, _ = cli.ImageRemove(ctx, imageName, types.ImageRemoveOptions{Force: true, PruneChildren: true})

	/* ---------- 3. 重新加载镜像 ---------- */
	if err := loadImage(ctx, cli, tarPath); err != nil {
		return "", err
	}

	/* ---------- 4. 镜像探活 ---------- */
	if ok, probeErr := probeImage(ctx, cli, imageName); !ok {
		return "", fmt.Errorf("镜像探活失败: %w", probeErr)
	}

	/* ---------- 5. 端口映射 ---------- */
	inspect, _, err := cli.ImageInspectWithRaw(ctx, imageName)
	if err != nil {
		return "", err
	}
	pb, ep := makePortMaps(inspect.Config.ExposedPorts)
	if len(ep) == 0 {
		return "", fmt.Errorf("image has no exposed ports")
	}

	/* ---------- 6. 创建并启动容器 ---------- */
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:        imageName,
		ExposedPorts: ep,
		Env:          []string{fmt.Sprintf("SUPABASE_URL=%s", getSupabaseURL())},
	}, &container.HostConfig{
		PortBindings: pb,
		RestartPolicy: container.RestartPolicy{
			Name: "unless-stopped",
		},
		Runtime: "nvidia",
		ShmSize: 2 * 1024 * 1024 * 1024,
	}, nil, nil, containerName)
	if err != nil {
		return "", err
	}
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return "", err
	}

	log.Infof("✅ cradle container started: %s", resp.ID)
	return resp.ID, nil
}

/* ---------- 辅助函数 ---------- */

// loadImage 只负责 docker load
// func loadImage(ctx context.Context, cli *client.Client, tarPath string) error {
// 	f, err := os.Open(tarPath)
// 	if err != nil {
// 		return err
// 	}
// 	defer f.Close()
// 	resp, err := cli.ImageLoad(ctx, f, true)
// 	if err != nil {
// 		return err
// 	}
// 	_ = resp.Body.Close()
// 	return nil
// }

func loadImage(ctx context.Context, cli *client.Client, tarPath string) error {
	f, err := os.Open(tarPath)
	if err != nil {
		return err
	}
	defer f.Close()

	resp, err := cli.ImageLoad(ctx, f, true)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// 尝试解析 tag 信息（ImageLoad 返回 JSON 流）
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		var line map[string]interface{}
		_ = json.Unmarshal(scanner.Bytes(), &line)
		if val, ok := line["stream"]; ok {
			log.Infof("镜像加载输出: %s", val)
		}
	}
	return nil
}

// probeImage: docker create --rm true，成功返回 (true,nil)
func probeImage(ctx context.Context, cli *client.Client, image string) (bool, error) {
	ctr, err := cli.ContainerCreate(ctx, &container.Config{Image: image, Cmd: []string{"true"}}, nil, nil, nil, "")
	if err != nil {
		return false, err
	}
	_ = cli.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{Force: true})
	return true, nil
}

// makePortMaps 把镜像里暴露的端口映射到本机同端口
func makePortMaps(src nat.PortSet) (nat.PortMap, nat.PortSet) {
	pm := nat.PortMap{}
	ep := nat.PortSet{}
	for p := range src {
		hostPort := p.Port() // 与容器端口同号
		pm[p] = []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: hostPort}}
		ep[p] = struct{}{}
	}
	return pm, ep
}

func DeployFromTarWithProgress(ctx context.Context, tarPath string, update func(progress int, phase string)) (string, error) {
	update(5, "5%")

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return "", fmt.Errorf("failed to create Docker client: %w", err)
	}

	tarFile, err := os.Open(tarPath)
	if err != nil {
		return "", fmt.Errorf("failed to open tar file: %w", err)
	}
	defer tarFile.Close()

	fi, err := tarFile.Stat()
	if err != nil {
		return "", fmt.Errorf("failed to get tar file size: %w", err)
	}
	totalSize := fi.Size()

	update(10, "10%")

	progressReader := &progressReader{
		r:     tarFile,
		total: totalSize,
		update: func(p int) {
			progress := 15 + p*20/100 // maps 0–100 → 15–35
			update(progress, fmt.Sprintf("%d%%", progress))
		},
	}

	loadResp, err := cli.ImageLoad(ctx, progressReader, true)
	if err != nil {
		return "", fmt.Errorf("failed to load image into Docker: %w", err)
	}
	defer loadResp.Body.Close()

	update(36, "36%")

	imageName, err := extractImageNameFromTar(tarPath)
	if err != nil {
		return "", fmt.Errorf("failed to extract image name from tar file: %w", err)
	}

	update(45, "45%")

	imageInspect, _, err := cli.ImageInspectWithRaw(ctx, imageName)
	if err != nil {
		return "", fmt.Errorf("failed to inspect image '%s': %w", imageName, err)
	}
	if len(imageInspect.Config.ExposedPorts) == 0 {
		return "", fmt.Errorf("image '%s' does not expose any ports", imageName)
	}

	portBindings := nat.PortMap{}
	exposedPorts := nat.PortSet{}
	for port := range imageInspect.Config.ExposedPorts {
		hostPort := port.Port()
		portBindings[port] = []nat.PortBinding{{
			HostIP:   "0.0.0.0",
			HostPort: hostPort,
		}}
		exposedPorts[port] = struct{}{}
	}

	update(60, "60%")

	containerName := imageName
	if strings.Contains(containerName, ":") {
		containerName = strings.SplitN(containerName, ":", 2)[0]
	}

	supabaseURL := getSupabaseURL()

	// log.Infof("============================>> Retrieved SUPABASE_URL = %s", supabaseURL)

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:        imageName,
		ExposedPorts: exposedPorts,
		Env: []string{
			fmt.Sprintf("SUPABASE_URL=%s", supabaseURL),
		},
	}, &container.HostConfig{
		PortBindings: portBindings,
		RestartPolicy: container.RestartPolicy{
			Name: "unless-stopped",
		},
		Runtime: "nvidia",
		ShmSize: 2 * 1024 * 1024 * 1024,
	}, nil, nil, containerName)
	if err != nil {
		return "", fmt.Errorf("failed to create container from image '%s': %w", imageName, err)
	}

	update(80, "80%")

	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return "", fmt.Errorf("failed to start container '%s': %w", containerName, err)
	}

	update(100, "100%")

	return resp.ID, nil
}

func DeployFromTarWithProgress_ch(ctx context.Context, tarPath string, update func(progress int, phase string)) (string, error) {
	update(5, "打开镜像文件")

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return "", fmt.Errorf("创建 Docker 客户端失败: %w", err)
	}

	tarFile, err := os.Open(tarPath)
	if err != nil {
		return "", fmt.Errorf("打开 tar 文件失败: %w", err)
	}
	defer tarFile.Close()

	// 获取镜像 tar 文件大小
	fi, err := tarFile.Stat()
	if err != nil {
		return "", fmt.Errorf("获取 tar 文件大小失败: %w", err)
	}
	totalSize := fi.Size()

	update(10, fmt.Sprintf("读取镜像文件完成，大小 %.2f MB", float64(totalSize)/1024/1024))

	// 包装进度统计 Reader
	progressReader := &progressReader{
		r:     tarFile,
		total: totalSize,
		update: func(p int) {
			// 映射上传过程的进度（占据从 15 到 35 的区间）
			progress := 15 + p*20/100
			update(progress, fmt.Sprintf("上传镜像中...（%d%%）", p))
		},
	}

	// 调用 Docker API 加载镜像
	loadResp, err := cli.ImageLoad(ctx, progressReader, true)
	if err != nil {
		return "", fmt.Errorf("镜像加载失败: %w", err)
	}
	defer loadResp.Body.Close()

	update(36, "镜像上传完毕，解析镜像名称")

	// 解析镜像名称
	imageName, err := extractImageNameFromTar(tarPath)
	if err != nil {
		return "", fmt.Errorf("提取镜像名失败: %w", err)
	}

	update(45, "检查镜像端口配置")

	imageInspect, _, err := cli.ImageInspectWithRaw(ctx, imageName)
	if err != nil {
		return "", fmt.Errorf("镜像检查失败: %w", err)
	}
	if len(imageInspect.Config.ExposedPorts) == 0 {
		return "", fmt.Errorf("镜像没有暴露端口")
	}

	portBindings := nat.PortMap{}
	exposedPorts := nat.PortSet{}
	for port := range imageInspect.Config.ExposedPorts {
		hostPort := port.Port()
		portBindings[port] = []nat.PortBinding{{
			HostIP:   "0.0.0.0",
			HostPort: hostPort,
		}}
		exposedPorts[port] = struct{}{}
	}

	update(60, "创建中...")

	containerName := imageName
	if strings.Contains(containerName, ":") {
		containerName = strings.SplitN(containerName, ":", 2)[0]
	}

	// 获取本地 IP 与默认网关在同一网段的地址
	supabaseURL := getSupabaseURL() // 👈 新增：调用获取 Supabase URL 的函数

	log.Infof("============================>>获取到的 SUPABASE_URL = %s", supabaseURL)

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:        imageName,
		ExposedPorts: exposedPorts,
		Env: []string{
			fmt.Sprintf("SUPABASE_URL=%s", supabaseURL), // 👈 新增：传入 SUPABASE_URL 环境变量
		},
	}, &container.HostConfig{
		PortBindings: portBindings,
		RestartPolicy: container.RestartPolicy{
			Name: "unless-stopped",
		},
		Runtime: "nvidia",
		ShmSize: 2 * 1024 * 1024 * 1024,
	}, nil, nil, containerName)
	if err != nil {
		return "", fmt.Errorf("创建失败: %w", err)
	}

	update(80, "启动中...")

	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return "", fmt.Errorf("模型启动失败: %w", err)
	}

	update(100, "部署完成")

	return resp.ID, nil
}

func mustParseAppID(appID string) int64 {
	id, _ := strconv.ParseInt(appID, 10, 64)
	return id
}

func (m *DeployCore) Deploy(req *proto.DeployDeployReq) (resp *proto.DeployDeployResp, code itypes.BizCode) {
	log.Infof(".......dal_core.................Deploy..........AppID=%s", req.AppID)

	if GlobalTaskManager.Get(req.AppID) != nil {
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "部署任务已存在")
	}

	appID, err := strconv.ParseInt(req.AppID, 10, 64)
	if err != nil {
		log.Errorf("invalid AppID: %s", req.AppID)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "invalid AppID")
	}

	if m.newsDAO == nil {
		log.Errorf("newsDAO is nil!")
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "newsDAO is nil")
	}

	do, err := m.newsDAO.QueryById(appID)
	if err != nil {
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	if do.Id == 0 {
		log.Warnf("QueryById returned nil for appID=%d", appID)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "app not found")
	}

	tarPath := do.Url
	if tarPath == "" {
		log.Errorf("tarPath 为空 (appID=%v)", appID)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "镜像路径为空")
	}

	repo_name := do.RepoName
	mbUUID := utils.GetMBUUID()

	task := GlobalTaskManager.Create(
		req.AppID,
		tarPath,
		m.deployDAO,
		req.UserName, // ✅ 传入 UserName
		repo_name,    // ✅ 传入 RepoName
		mbUUID,       // ✅ 传入 MbUuid
	)
	if task == nil {
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "无法创建部署任务")
	}

	sameUrlNewsList, err := m.newsDAO.QueryAllByUrl(tarPath)
	if err != nil {
		log.Errorf("查询相同 URL 的记录失败: %v", err)
		// 可选：return 或继续执行
	}
	bytes, _ := json.MarshalIndent(sameUrlNewsList, "", "  ")
	log.Infof("=====================>sameUrlNewsList=\n%s", string(bytes))
	for _, news := range sameUrlNewsList {
		deployRecord := &models.DeployDO{
			Nid:         int32(news.Id),
			Status:      "deploying",
			UserName:    req.UserName,
			RepoName:    repo_name,
			MbUuid:      mbUUID,
			CreatedTime: time.Now(),
		}
		if _, err := m.deployDAO.Insert(deployRecord); err != nil {
			log.Errorf("写入部署记录失败 (nid=%d): %v", news.Id, err)
		}
	}

	return &proto.DeployDeployResp{}, itypes.BizOK
}

func (m *DeployCore) Status(req *proto.DeployStatusReq) (resp *proto.DeployStatusResp, code itypes.BizCode) {
	log.Infof(".......dal_core.................DeployStatus..........AppID=%s", req.AppID)
	appID, err := strconv.ParseInt(req.AppID, 10, 64)
	if err != nil {
		log.Errorf("invalid AppID: %s", req.AppID)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "invalid AppID")
	}

	if m.newsDAO == nil {
		log.Errorf("newsDAO is nil!")
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "internal error: newsDAO is nil")
	}

	do, err := m.newsDAO.QueryById(appID)
	if err != nil {
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	if do.Id == 0 {
		log.Warnf("QueryById returned nil for appID=%d", appID)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "app not found")
	}

	do1, err := m.deployDAO.QueryByNid(appID)
	if err != nil {
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	if do1.Id == 0 {
		log.Warnf("QueryByNid returned nil for nID=%d", appID)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "no deploy record found")
	}

	log.Infof("返回部署记录 ID=%d，状态=%s", do1.Id, do1.Status)

	// 返回完整记录
	return &proto.DeployStatusResp{
		Data: do1,
	}, itypes.BizOK
}

func (m *DeployCore) Delete(req *proto.DeployDeleteReq) (resp *proto.DeployDeleteResp, code itypes.BizCode) {
	log.Infof(".......dal_core.................DeployDelete..........NID=%s, User=%s", req.Nid, req.UserName)

	nid, err := strconv.ParseInt(req.Nid, 10, 64)
	if err != nil {
		log.Errorf("非法的 n_id: %s", req.Nid)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "非法的 n_id")
	}

	// 1. 查找部署记录
	do, err := m.deployDAO.QueryByNid(nid)
	if err != nil {
		log.Errorf("查询部署记录失败: %v", err)
		return nil, itypes.NewBizCodeDatabaseError("数据库查询失败")
	}
	if do == nil || do.Id == 0 {
		log.Warnf("未找到部署记录, Nid=%s", req.Nid)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "部署记录不存在")
	}
	if do.UserName != req.UserName {
		log.Warnf("用户不匹配，拒绝删除：req.UserName=%s, record.UserName=%s", req.UserName, do.UserName)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "没有权限删除该容器")
	}

	repoName := do.RepoName
	imageRef := do.RepoName + ":latest"

	if repoName == "" {
		log.Errorf("repo_name 为空，不能删除容器")
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "repo_name 为空")
	}

	// 2. 初始化 Docker 客户端
	dockerCli, err := client.NewClientWithOpts(client.FromEnv)
	if err != nil {
		log.Errorf("初始化 Docker 客户端失败: %v", err)
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "Docker 初始化失败")
	}
	defer dockerCli.Close()
	ctx := context.Background()

	// 3. 停止容器
	timeout := 10 * time.Second
	log.Infof("尝试停止容器：%s", repoName)
	if err := dockerCli.ContainerStop(ctx, repoName, &timeout); err != nil {
		log.Warnf("停止容器失败（可能已退出）：%v", err)
	}

	// 4. 等待容器状态为 exited
	maxRetry := 10
	for i := 0; i < maxRetry; i++ {
		inspect, err := dockerCli.ContainerInspect(ctx, repoName)
		if err != nil {
			if client.IsErrNotFound(err) {
				break // 容器已不存在，跳出
			}
			log.Errorf("查询容器状态失败: %v", err)
			return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "查询容器状态失败")
		}

		status := inspect.State.Status
		log.Infof("当前容器状态: %s", status)

		if status == "exited" || status == "dead" {
			break
		}
		time.Sleep(1 * time.Second)
	}

	// 5. 删除容器
	log.Infof("尝试删除容器：%s", repoName)
	if err := dockerCli.ContainerRemove(ctx, repoName, types.ContainerRemoveOptions{Force: false}); err != nil {
		log.Errorf("删除容器失败: %v", err)
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "容器删除失败: "+err.Error())
	}

	if imageRef != "" {
		log.Infof("尝试删除镜像：%s", imageRef)
		_, err := dockerCli.ImageRemove(ctx, imageRef, types.ImageRemoveOptions{
			Force:         true,
			PruneChildren: true,
		})
		if err != nil {
			log.Warnf("镜像删除失败（可忽略）: %v", err)
		} else {
			log.Infof("✅ 镜像删除成功: %s", imageRef)
		}
	}

	// 7. 删除数据库记录
	// if err := m.deployDAO.DeleteByNid(nid); err != nil {
	// 	log.Warnf("从数据库中删除部署记录失败: %v", err)
	// }

	// 🔍 查出一条记录，提取 mb_uuid/repo_name/user_name 作为匹配条件
	record, err := m.deployDAO.QueryByNid(nid)
	if err != nil {
		log.Warnf("查询部署记录失败: %v", err)
	} else {
		if err := m.deployDAO.DeleteByFields(record.MbUuid, record.RepoName, record.UserName); err != nil {
			log.Warnf("批量删除部署记录失败: %v", err)
		}
	}

	log.Infof("✅ 容器删除成功，Nid=%d", nid)
	return &proto.DeployDeleteResp{}, itypes.BizOK
}

// func (m *DeployCore) Start(req *proto.DeployStartReq) (resp *proto.DeployStartResp, code itypes.BizCode) {
// 	log.Infof(".......dal_core.................DeployStart..........NID=%s, User=%s", req.Nid, req.UserName)

// 	nid, err := strconv.ParseInt(req.Nid, 10, 64)
// 	if err != nil {
// 		log.Errorf("非法的 n_id: %s", req.Nid)
// 		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "非法的 n_id")
// 	}

// 	// 1. 查找部署记录
// 	do, err := m.deployDAO.QueryByNid(nid)
// 	if err != nil {
// 		log.Errorf("查询部署记录失败: %v", err)
// 		return nil, itypes.NewBizCodeDatabaseError("数据库查询失败")
// 	}
// 	if do == nil || do.Id == 0 {
// 		log.Warnf("未找到部署记录, Nid=%s", req.Nid)
// 		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "部署记录不存在")
// 	}
// 	if do.UserName != req.UserName {
// 		log.Warnf("用户不匹配，拒绝删除：req.UserName=%s, record.UserName=%s", req.UserName, do.UserName)
// 		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "没有权限删除该容器")
// 	}

// 	repoName := do.RepoName
// 	imageRef := do.RepoName + ":latest"

// 	if repoName == "" {
// 		log.Errorf("repo_name 为空，不能删除容器")
// 		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "repo_name 为空")
// 	}

// 	// 2. 初始化 Docker 客户端
// 	dockerCli, err := client.NewClientWithOpts(client.FromEnv)
// 	if err != nil {
// 		log.Errorf("初始化 Docker 客户端失败: %v", err)
// 		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "Docker 初始化失败")
// 	}
// 	defer dockerCli.Close()
// 	ctx := context.Background()

// 	// 3. 停止容器
// 	timeout := 10 * time.Second
// 	log.Infof("尝试停止容器：%s", repoName)
// 	if err := dockerCli.ContainerStop(ctx, repoName, &timeout); err != nil {
// 		log.Warnf("停止容器失败（可能已退出）：%v", err)
// 	}

// 	// 4. 等待容器状态为 exited
// 	maxRetry := 10
// 	for i := 0; i < maxRetry; i++ {
// 		inspect, err := dockerCli.ContainerInspect(ctx, repoName)
// 		if err != nil {
// 			if client.IsErrNotFound(err) {
// 				break // 容器已不存在，跳出
// 			}
// 			log.Errorf("查询容器状态失败: %v", err)
// 			return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "查询容器状态失败")
// 		}

// 		status := inspect.State.Status
// 		log.Infof("当前容器状态: %s", status)

// 		if status == "exited" || status == "dead" {
// 			break
// 		}
// 		time.Sleep(1 * time.Second)
// 	}

// 	// 5. 删除容器
// 	log.Infof("尝试删除容器：%s", repoName)
// 	if err := dockerCli.ContainerRemove(ctx, repoName, types.ContainerRemoveOptions{Force: false}); err != nil {
// 		log.Errorf("删除容器失败: %v", err)
// 		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "容器删除失败: "+err.Error())
// 	}

// 	if imageRef != "" {
// 		log.Infof("尝试删除镜像：%s", imageRef)
// 		_, err := dockerCli.ImageRemove(ctx, imageRef, types.ImageRemoveOptions{
// 			Force:         true,
// 			PruneChildren: true,
// 		})
// 		if err != nil {
// 			log.Warnf("镜像删除失败（可忽略）: %v", err)
// 		} else {
// 			log.Infof("✅ 镜像删除成功: %s", imageRef)
// 		}
// 	}

// 	// 7. 删除数据库记录
// 	// if err := m.deployDAO.DeleteByNid(nid); err != nil {
// 	// 	log.Warnf("从数据库中删除部署记录失败: %v", err)
// 	// }

// 	// 🔍 查出一条记录，提取 mb_uuid/repo_name/user_name 作为匹配条件
// 	record, err := m.deployDAO.QueryByNid(nid)
// 	if err != nil {
// 		log.Warnf("查询部署记录失败: %v", err)
// 	} else {
// 		if err := m.deployDAO.DeleteByFields(record.MbUuid, record.RepoName, record.UserName); err != nil {
// 			log.Warnf("批量删除部署记录失败: %v", err)
// 		}
// 	}

// 	log.Infof("✅ 容器删除成功，Nid=%d", nid)
// 	return &proto.DeployStartResp{}, itypes.BizOK
// }

func (m *DeployCore) Start(req *proto.DeployStartReq) (resp *proto.DeployStartResp, code itypes.BizCode) {
	log.Infof(".......dal_core.................DeployStart..........NID=%s, User=%s", req.Nid, req.UserName)

	nid, err := strconv.ParseInt(req.Nid, 10, 64)
	if err != nil {
		log.Errorf("Invalid n_id: %s", req.Nid)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "Invalid n_id")
	}

	// 1. Query deploy record
	do, err := m.deployDAO.QueryByNid(nid)
	if err != nil {
		log.Errorf("Failed to query deploy record: %v", err)
		return nil, itypes.NewBizCodeDatabaseError("Database query failed")
	}
	if do == nil || do.Id == 0 {
		log.Warnf("No deploy record found, Nid=%s", req.Nid)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "Deploy record not found")
	}
	if do.UserName != req.UserName {
		log.Warnf("User mismatch, permission denied: req.UserName=%s, record.UserName=%s", req.UserName, do.UserName)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "No permission to start this container")
	}

	repoName := do.RepoName
	if repoName == "" {
		log.Errorf("repo_name is empty, cannot start container")
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "repo_name is empty")
	}

	// 2. Init Docker client
	dockerCli, err := client.NewClientWithOpts(client.FromEnv)
	if err != nil {
		log.Errorf("Failed to initialize Docker client: %v", err)
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "Docker initialization failed")
	}
	defer dockerCli.Close()
	ctx := context.Background()

	// 3. Start the container
	log.Infof("Attempting to start container: %s", repoName)
	if err := dockerCli.ContainerStart(ctx, repoName, types.ContainerStartOptions{}); err != nil {
		log.Errorf("Failed to start container '%s': %v", repoName, err)
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "Failed to start container: "+err.Error())
	}

	// 4. Update DB status to "running"
	if err := m.deployDAO.UpdateStatusByFields(do.MbUuid, do.RepoName, do.UserName, "running"); err != nil {
		log.Warnf("Container started, but failed to update DB: %v", err)
	}

	log.Infof("✅ Container started successfully: %s", repoName)
	return &proto.DeployStartResp{}, itypes.BizOK
}

func (m *DeployCore) Stop(req *proto.DeployStopReq) (resp *proto.DeployStopResp, code itypes.BizCode) {
	log.Infof(".......dal_core.................DeployStop..........NID=%s, User=%s", req.Nid, req.UserName)

	nid, err := strconv.ParseInt(req.Nid, 10, 64)
	if err != nil {
		log.Errorf("Invalid nid: %s", req.Nid)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "Invalid nid")
	}

	// 1. Query deployment record
	do, err := m.deployDAO.QueryByNid(nid)
	if err != nil {
		log.Errorf("Failed to query deployment record: %v", err)
		return nil, itypes.NewBizCodeDatabaseError("Failed to query deployment record")
	}
	if do == nil || do.Id == 0 {
		log.Warnf("Deployment record not found, Nid=%s", req.Nid)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "Deployment not found")
	}
	if do.UserName != req.UserName {
		log.Warnf("Permission denied: req.UserName=%s, record.UserName=%s", req.UserName, do.UserName)
		return nil, itypes.NewBizCode(itypes.CODE_NOT_FOUND, "No permission to stop this container")
	}

	repoName := do.RepoName
	if repoName == "" {
		log.Errorf("repo_name is empty, cannot stop container")
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "repo_name is empty")
	}

	// 2. Init Docker client
	dockerCli, err := client.NewClientWithOpts(client.FromEnv)
	if err != nil {
		log.Errorf("Failed to initialize Docker client: %v", err)
		return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "Docker initialization failed")
	}
	defer dockerCli.Close()
	ctx := context.Background()

	// 3. Stop container
	timeout := 10 * time.Second
	log.Infof("Attempting to stop container: %s", repoName)
	if err := dockerCli.ContainerStop(ctx, repoName, &timeout); err != nil {
		log.Warnf("Failed to stop container (might already be stopped): %v", err)
	}

	// 4. Wait for container to be exited or dead
	maxRetry := 10
	for i := 0; i < maxRetry; i++ {
		inspect, err := dockerCli.ContainerInspect(ctx, repoName)
		if err != nil {
			if client.IsErrNotFound(err) {
				break // container not found
			}
			log.Errorf("Failed to inspect container: %v", err)
			return nil, itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, "Failed to inspect container status")
		}

		status := inspect.State.Status
		log.Infof("Current container status: %s", status)

		if status == "exited" || status == "dead" {
			break
		}
		time.Sleep(1 * time.Second)
	}

	// 5. Update status in database
	if err := m.deployDAO.UpdateStatusByFields(do.MbUuid, do.RepoName, do.UserName, "stopped"); err != nil {
		log.Warnf("Failed to update deployment status in database: %v", err)
	}

	log.Infof("✅ Container stopped successfully, Nid=%d", nid)
	return &proto.DeployStopResp{}, itypes.BizOK
}
