package image

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/client"
	"github.com/klauspost/pgzip"
	image2 "wdd.io/agent-common/image"
	"wdd.io/agent-common/logger"
	"wdd.io/agent-common/utils"
	"wdd.io/agent-deploy/d_app"
)

var apiClient = newClient()
var log = logger.Log

const OfflineImageGzipFolderPrefix = "/var/lib/docker/wdd/octopus_image/"

func newClient() *client.Client {
	apiClient, err := client.NewClientWithOpts(client.FromEnv)

	if err != nil {
		log.ErrorF("[newClient] - new client failed ! => %s", err.Error())
		panic(err)
	}
	defer apiClient.Close()

	return apiClient
}

func GetRunningContainer() []types.Container {
	return getContainerList(false)
}

func GetAllContainer() []types.Container {
	return getContainerList(true)
}

func getContainerList(all bool) []types.Container {

	containers, err := apiClient.ContainerList(context.Background(), types.ContainerListOptions{
		Quiet:   false,
		Size:    false,
		All:     all,
		Latest:  false,
		Limit:   0,
		Filters: filters.Args{},
	})
	if err != nil {
		log.ErrorF("[getContainerList] - error => %s", err.Error())
	}

	return containers
}

//func SetDockerProxy(httpProxy string) {
//	proxy_all := "HTTP_PROXY=" + httpProxy + " HTTPS_PROXY=" + httpProxy
//
//	log.InfoF("[SetDockerProxy] - set docker proxy to %s", proxy_all)
//
//	// Set the HTTP proxy for the Docker daemon
//	configFile, err := apiClient.ConfigCreate(context.Background(), swarm.ConfigSpec{
//		Annotations: swarm.Annotations{
//			Name: "proxy.conf",
//		},
//		Data: []byte(proxy_all),
//	})
//	if err != nil {
//		// 处理错误
//		log.ErrorF("[SetDockerProxy] - set docker proxy error %s", err.Error())
//	}
//
//
//
//
//}

func GetAll() []types.ImageSummary {

	list, err := apiClient.ImageList(context.TODO(), types.ImageListOptions{
		All:     true,
		Filters: filters.Args{},
	})
	if err != nil {
		log.ErrorF("[ImageGetAll] --error => %s", err.Error())
	}

	return list
}

func GetByName(imageName string) *types.ImageSummary {

	imageGetAll := GetAll()

	for _, imageSummary := range imageGetAll {
		for _, tag := range imageSummary.RepoTags {
			if strings.Contains(tag, imageName) {
				return &imageSummary
			}
		}
	}
	return nil
}

func Delete(imageName string) []types.ImageDeleteResponseItem {
	imageGetByName := GetByName(imageName)
	if imageGetByName == nil {
		log.ErrorF("[ImageDelete] -- image not exists ! %s", imageGetByName.RepoTags)
		return nil
	}

	remove, err := apiClient.ImageRemove(context.TODO(), imageGetByName.ID, types.ImageRemoveOptions{
		Force:         true,
		PruneChildren: false,
	})
	if err != nil {
		log.ErrorF("[ImageDelete] -- ImageRemove error  ! %s", err.Error())
		return nil
	}

	return remove
}

func PruneAllCmiiImages() (errorRemoveImageNameList []string) {

	_, _ = apiClient.ImagesPrune(context.TODO(), filters.Args{})

	imageGetAll := GetAll()

	// ip:8033
	//re := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}:\d{1,4}`)

	for _, imageSummary := range imageGetAll {
		for _, repoTag := range imageSummary.RepoTags {
			if strings.HasPrefix(repoTag, image2.CmiiHarborPrefix) || strings.HasPrefix(repoTag, "harbor.wdd.io") || strings.Contains(repoTag, ":8033") {
				for _, tag := range imageSummary.RepoTags {
					_, err := apiClient.ImageRemove(context.TODO(), imageSummary.ID, types.ImageRemoveOptions{
						Force:         true,
						PruneChildren: true,
					})
					if err != nil {
						log.ErrorF("[ImageDelete] -- ImageRemove error  ! %s", err.Error())
						errorRemoveImageNameList = append(errorRemoveImageNameList, tag)
					}
					log.InfoF("[ImageDelete] - image remove of [%s] success!", tag)
				}
			}
		}
	}

	_, _ = apiClient.ImagesPrune(context.TODO(), filters.Args{})

	return errorRemoveImageNameList
}

func TagFromSourceToTarget(sourceImageName, targetImageName string) bool {

	getByName := GetByName(sourceImageName)
	if getByName == nil {
		log.ErrorF("[ImageTagFromSourceToTarget] -  %s not exits !", sourceImageName)
		return false
	}

	sourceImageName = getByName.RepoTags[0]
	err := apiClient.ImageTag(context.TODO(), sourceImageName, targetImageName)
	if err != nil {
		log.ErrorF("[ImageTagFromSourceToTarget] - from %s to %s error %s", sourceImageName, targetImageName, err.Error())
	}

	log.InfoF("[ImageTagFromSourceToTarget] - from %s to %s success!", sourceImageName, targetImageName)
	return true
}

func UploadToOctopusKindHarbor(targetImageName string) (pushResult io.ReadCloser) {

	if GetByName(targetImageName) == nil {
		log.ErrorF("[ImagePushToOctopusKindHarbor] - %s not exits !", targetImageName)
		return pushResult
	}

	pushResult, err := apiClient.ImagePush(context.TODO(), targetImageName, types.ImagePushOptions{
		All: false,
		//RegistryAuth: "eyAidXNlcm5hbWUiOiAiYWRtaW4iLCAicGFzc3dvcmQiOiAiVjJyeVN0ckBuZ1BzcyIsICJlbWFpbCI6ICJpY2VAcXEuY29tIiB9Cg==",
		// 重庆移动二级平台
		RegistryAuth:  "eyAidXNlcm5hbWUiOiAiemd5ZHR4anRjcXl4Z3MxODg4MzI1NzMxMSIsICJwYXNzd29yZCI6ICJEaWN0QDIwMjQiLCAiZW1haWwiOiAiaWNlQHFxLmNvbSIgfQ==",
		PrivilegeFunc: nil,
		Platform:      "amd64",
	})
	if err != nil {
		log.ErrorF("[ImagePushToOctopusKindHarbor] - push %s error %s", targetImageName, err.Error())
		return nil
	}

	return pushResult
}

func UploadToHarbor(targetImageName string) (uploadOK bool) {

	pushResult := UploadToOctopusKindHarbor(targetImageName)

	defer pushResult.Close()

	scanner := bufio.NewScanner(pushResult)
	for scanner.Scan() {
	}

	fmt.Println()
	log.InfoF("[UploadToHarbor] - upload %s success!", targetImageName)
	fmt.Println()

	return true
}

// TagFromListAndPushToCHarbor 需要支持 harbor.cdcyy.cn  ip:8033  rancher/rancher:v2.5.7  nginx:latest
func TagFromListAndPushToCHarbor(referenceImageList []string, targetHarborHost string) (errorPushImageNameList []string) {

	targetHarborHost = strings.TrimPrefix(targetHarborHost, "http://")
	targetHarborHost = strings.TrimPrefix(targetHarborHost, "https://")

	for _, imageName := range referenceImageList {

		// check image
		//  harbor.cdcyy.cn
		cmiiImageFullName := imageName

		// convert image name
		targetImageName := image2.ImageNameToTargetImageFullName(imageName, targetHarborHost)

		if TagFromSourceToTarget(cmiiImageFullName, targetImageName) {
			pushResult := UploadToOctopusKindHarbor(targetImageName)
			if pushResult == nil {
				errorPushImageNameList = append(errorPushImageNameList, cmiiImageFullName)
				log.InfoF("[ImageTagFromListAndPushToCHarbor] - push of %s error  error !", targetImageName)
				break
			}
			scanner := bufio.NewScanner(pushResult)
			for scanner.Scan() {
				text := scanner.Text()
				if strings.Contains(text, "digest: sha256:") {
					fmt.Println(scanner.Text())
				}
			}
			log.InfoF("[ImageTagFromListAndPushToCHarbor] - push of %s success!", targetImageName)
			fmt.Println()
		} else {
			errorPushImageNameList = append(errorPushImageNameList, cmiiImageFullName)
		}

	}

	return errorPushImageNameList
}

func PullFromDockerHub(imageName string) (pullResult io.ReadCloser) {

	var imagePullOptions types.ImagePullOptions
	if strings.HasPrefix(imageName, image2.CmiiHarborPrefix) {
		imagePullOptions = types.ImagePullOptions{
			All:          false,
			RegistryAuth: "eyAidXNlcm5hbWUiOiAicmFkMDJfZHJvbmUiLCAicGFzc3dvcmQiOiAiRHJvbmVAMTIzNCIsICJlbWFpbCI6ICJpY2VAcXEuY29tIiB9Cg==",
			PrivilegeFunc: func() (string, error) {
				return "authorization: Basic cmFkMDJfZHJvbmU6RHJvbmVAMTIzNA==", nil
			},
		}
	}

	pullResult, err := apiClient.ImagePull(context.TODO(), imageName, imagePullOptions)
	if err != nil {
		log.ErrorF("[PullFromDockerHub]- image pull of %s ,error => %s", imageName, err.Error())
		return nil
	}

	return pullResult
}

func PullFromCmiiHarborByMap(imageVersionMap map[string]string, silentMode bool) (fullImageNameList, errorPullImageList []string) {

	fullImageNameList = CmiiImageMapToFullNameList(imageVersionMap)
	return fullImageNameList, PullFromFullNameList(fullImageNameList)

}

func PullCmiiFromFileJson(filePathName string) {

	readFile, err := os.ReadFile(filePathName)
	if err != nil {
		log.ErrorF("[ImagePullCMiiFromFileJson] - file %s read error ! %s", filePathName, err.Error())
		return
	}

	var resultMap map[string]string
	err = json.Unmarshal(readFile, &readFile)
	if err != nil {
		log.ErrorF("[ImagePullCMiiFromFileJson] - file %s un marshal error ! %s", filePathName, err.Error())
		return
	}

	for image, tag := range resultMap {
		pullResult := PullFromDockerHub(image + ":" + tag)
		if pullResult == nil {
			continue
		}
		scanner := bufio.NewScanner(pullResult)
		for scanner.Scan() {
			line := scanner.Text()
			if strings.Contains(line, "\"status\":\"Pulling from") {
				fmt.Println(line)
			}
			if strings.Contains(line, "Status: Image is up to date for") {
				fmt.Println(line)
			}
		}
		fmt.Println()
	}

}

// PullFromFullNameList 根据镜像名列表拉取全部的镜像
func PullFromFullNameList(fullImageNameList []string) (errorPullImageList []string) {

	//start := time.Now()
	for _, fullImageName := range fullImageNameList {
		//if !strings.HasPrefix(fullImageName, "harbor.cdcyy.com.cn") {
		//    since := time.Since(start)
		//    if since < 90*time.Second {
		//        duration := 90*time.Second - since
		//        log.DebugF("PullFromFullNameList - wait for %s !", duration.String())
		//        time.Sleep(duration)
		//        start = time.Now()
		//    }
		//}
		log.DebugF("start to pull => [%s]", fullImageName)
		pullResult := PullFromDockerHub(fullImageName)
		if pullResult == nil {
			errorPullImageList = append(errorPullImageList, fullImageName)
			continue
		}
		scanner := bufio.NewScanner(pullResult)
		for scanner.Scan() {
			line := scanner.Text()
			//if strings.Contains(line, "\"status\":\"Pulling from") {
			//	fmt.Println(line)
			//}
			if strings.Contains(line, "Status: Image is up to date for") {
				fmt.Println(line)
			}
		}
		fmt.Println()
	}

	return errorPullImageList
}

func PullFromListAndCompressSplit(fullImageNameList []string, gzipFolder string) (errorPullImageList, errorGzipImageList []string) {

	errorPullImageList = PullFromFullNameList(fullImageNameList)

	// generate a project folder
	err := os.MkdirAll(gzipFolder, os.ModeDir)
	if err != nil {
		if !errors.Is(err, os.ErrExist) {
			log.ErrorF("[ImagePullFromListAndCompressSplit] - create folder of %s error %s", gzipFolder, err.Error())
		}
	}

	var tarGzipFileNameList []string

	for _, image := range fullImageNameList {

		ok, path := SaveToGzipFile(image, gzipFolder)
		if !ok {
			errorGzipImageList = append(errorGzipImageList, image)
			continue
		}

		tarGzipFileNameList = append(tarGzipFileNameList, path)
	}

	utils.BeautifulPrintListWithTitle(tarGzipFileNameList, "image gzip name list")
	return errorPullImageList, errorGzipImageList
}

// LoadFromGzipFilePath 根据Gzip文件的全名称进行Load操作
func LoadFromGzipFilePath(gzipFileNameFullPath string) bool {
	openFile, err := os.OpenFile(gzipFileNameFullPath, 0, fs.ModePerm)
	if err != nil {
		log.ErrorF("[ImageLoadFromFile] - failed to open file %s, error is %s", gzipFileNameFullPath, err.Error())
		return false
	}

	loadResponse, err := apiClient.ImageLoad(context.TODO(), openFile, true)
	if err != nil {
		log.ErrorF("[ImageLoadFromFile] - load error %s, error is %s", gzipFileNameFullPath, err.Error())
		return false
	}

	log.InfoF("[ImageLoadFromFile] - load of %s, result is %s", gzipFileNameFullPath, strconv.FormatBool(loadResponse.JSON))

	scanner := bufio.NewScanner(loadResponse.Body)
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Println(line)
	}

	return true
}

func LoadFromFolderPath(folderPath string) (errorLoadImageNameList []string) {

	if !strings.HasSuffix(folderPath, "/") {
		folderPath += "/"
	}

	dirEntries, err := os.ReadDir(folderPath)
	if err != nil {
		log.ErrorF("[ImageLoadFromFolderPath] - error read folder %s error is %s", folderPath, err.Error())
		return
	}

	// load gzip file
	for _, dirEntry := range dirEntries {
		if strings.HasSuffix(dirEntry.Name(), ".tar.gz") {
			if !LoadFromGzipFilePath(folderPath + dirEntry.Name()) {
				errorLoadImageNameList = append(errorLoadImageNameList, folderPath+dirEntry.Name())
			}
		}
	}

	return errorLoadImageNameList
}

// SaveToGzipFile 根据目标镜像的全名称 将镜像压缩为Gzip文件
func SaveToGzipFile(imageFullName, folderPathPrefix string) (gzipOK bool, gzipImageFileFullPath string) {

	imageGetByName := GetByName(imageFullName)
	if imageGetByName == nil {
		log.ErrorF("[ImageSaveToTarGZ] - %s not exits", imageFullName)
		return false, ""
	}

	imageSaveTarStream, err := apiClient.ImageSave(context.TODO(), imageGetByName.RepoTags)
	if err != nil {
		log.ErrorF("[ImageSaveToTarGZ] - image save error %s", err.Error())
		return false, ""
	}

	var realImageTag string
	for _, repoTag := range imageGetByName.RepoTags {
		if !strings.Contains(repoTag, "8033") {
			realImageTag = repoTag
			break
		}
	}

	gzipImageFileFullPath = image2.ImageFullNameToGzipFileName(realImageTag)

	if err := os.MkdirAll(filepath.Dir(gzipImageFileFullPath), os.ModePerm); err != nil {
		log.ErrorF("[ImageSaveToTarGZ] - failed to create directory: %s", err)
		return false, ""
	}

	// 生成gzip压缩文件的全路径名称
	gzipImageFileFullPath = filepath.Join(folderPathPrefix, gzipImageFileFullPath)

	log.InfoF("[ImageSaveToTarGZ] - start to save [%s] to [%s]", realImageTag, gzipImageFileFullPath)

	// 删除掉旧的Gzip文件
	if err := os.Remove(gzipImageFileFullPath); err != nil && !os.IsNotExist(err) {
		log.ErrorF("[ImageSaveToTarGZ] - failed to remove old gzip file: %s", err)
		return false, ""
	}

	// 创建
	tarFile, err := os.Create(gzipImageFileFullPath)
	if err != nil {
		log.ErrorF("[ImageSaveToTarGZ] - error create gzip %s file ! => %s ", gzipImageFileFullPath, err.Error())
		return false, ""
	}
	defer tarFile.Close()

	// 创建pgzip writer
	gw, err := pgzip.NewWriterLevel(tarFile, pgzip.DefaultCompression) // 你可以调整压缩级别
	if err != nil {
		log.ErrorF("[ImageSaveToTarGZ] - pgzip create writer error: %s", err.Error())
	}
	defer gw.Close()

	// Copy the tar archive to the gzip writer.
	if _, err := io.Copy(gw, imageSaveTarStream); err != nil {
		log.ErrorF("[ImageSaveToTarGZ] - failed to copy tar archive to gzip writer: %s", err.Error())
		return false, ""
	}

	// 成功
	return true, gzipImageFileFullPath
}

// SaveImageListToGzipFile 将一个列表内的镜像全部压缩为一个tar.gz文件
func SaveImageListToGzipFile(imageFullNames []string, folderPathPrefix string, outputFileName string) (gzipOK bool, gzipFileFullPath string, errorGzipImageList []string) {

	//if len(imageFullNames) == 0 {
	//	log.Error("[SaveImagesToGzipFile] - no images provided")
	//	return false, "", errorGzipImageList
	//}
	//
	//// 确保输出文件路径
	//gzipFileFullPath = folderPathPrefix + outputFileName
	//if err := os.MkdirAll(filepath.Dir(gzipFileFullPath), os.ModePerm); err != nil {
	//	log.ErrorF("[SaveImagesToGzipFile] - failed to create directory: %s", err)
	//	return false, "", errorGzipImageList
	//}
	//
	//log.InfoF("[SaveImagesToGzipFile] - start saving images to [%s]", gzipFileFullPath)
	//
	//// 删除旧的Gzip文件
	//if err := os.removePrefix(gzipFileFullPath); err != nil && !os.IsNotExist(err) {
	//	log.ErrorF("[SaveImagesToGzipFile] - failed to remove old gzip file: %s", err)
	//	return false, "", errorGzipImageList
	//}
	//
	//tarFile, err := os.Create(gzipFileFullPath)
	//if err != nil {
	//	log.ErrorF("[SaveImagesToGzipFile] - error creating gzip file: %s", err)
	//	return false, "", errorGzipImageList
	//}
	//defer tarFile.Close()
	//
	//gw, err := pgzip.NewWriterLevel(tarFile, pgzip.DefaultCompression)
	//if err != nil {
	//	log.ErrorF("[SaveImagesToGzipFile] - pgzip writer creation error: %s", err)
	//	return false, "", errorGzipImageList
	//}
	//defer gw.Close()
	//
	//for _, imageFullName := range imageFullNames {
	//	imageGetByName := GetByName(imageFullName)
	//	if imageGetByName == nil {
	//		log.WarnF("[SaveImagesToGzipFile] - %s not exists, skipping", imageFullName)
	//		continue
	//	}
	//
	//	imageSaveTarStream, err := apiClient.ImageSave(context.TODO(), imageGetByName.RepoTags)
	//	if err != nil {
	//		log.ErrorF("[SaveImagesToGzipFile] - image save error for %s: %s", imageFullName, err)
	//		continue
	//	}
	//
	//	if _, err := io.Copy(gw, imageSaveTarStream); err != nil {
	//		log.ErrorF("[SaveImagesToGzipFile] - failed to copy tar archive for %s to gzip writer: %s", imageFullName, err)
	//		continue
	//	}
	//
	//}
	//
	//if err := gw.Close(); err != nil {
	//	log.ErrorF("[SaveImagesToGzipFile] - error closing gzip writer: %s", err)
	//	return false, "", errorGzipImageList
	//}
	//
	//log.InfoF("[SaveImagesToGzipFile] - successfully saved images to [%s]", gzipFileFullPath)
	return true, gzipFileFullPath, errorGzipImageList

}

func CmiiImageMapToFullNameList(cmiiImageVersionMap map[string]string) (fullImageNameList []string) {

	for image, tag := range cmiiImageVersionMap {
		s := image2.CmiiHarborPrefix + image + ":" + tag
		fullImageNameList = append(fullImageNameList, s)
	}

	return fullImageNameList
}

func CmiiImageMapFromGzipFolder(gzipFileFolder string) (cmiiImageVersionMap map[string]string) {

	allFileInFolder, err := utils.ListAllFileInFolder(gzipFileFolder)
	if err != nil {
		return nil
	}

	cmiiImageVersionMap = make(map[string]string)
	for _, gzipFileName := range allFileInFolder {
		log.DebugF("gzip file name is %s", gzipFileName)
		imageName, imageTag := image2.GzipFileNameToImageNameAndTag(gzipFileName)
		cmiiImageVersionMap[imageName] = imageTag
	}

	return cmiiImageVersionMap
}

// GenerateCmiiTagVersionImageMap 生成特定版本的ImageTagMap
func GenerateCmiiTagVersionImageMap(specificTag string) (backendMap, frontendMap, srsMap map[string]string) {
	matched, _ := regexp.MatchString(`^\d+\.\d+\.\d+$`, specificTag)
	if !matched {
		sprintf := fmt.Sprintf("tag is not match !  [%s]", specificTag)
		log.Error(sprintf)
		panic(sprintf)
	}

	backendMap = make(map[string]string, len(d_app.CmiiBackendAppMap))
	frontendMap = make(map[string]string, len(d_app.CmiiFrontendAppMap))
	srsMap = make(map[string]string, len(d_app.CmiiSrsAppMap))

	for imageName := range d_app.CmiiBackendAppMap {
		backendMap[imageName] = specificTag
	}
	for imageName := range d_app.CmiiFrontendAppMap {
		frontendMap[imageName] = specificTag
	}
	for imageName, imageTag := range d_app.CmiiSrsAppMap {
		srsMap[imageName] = imageTag
	}

	return backendMap, frontendMap, srsMap
}

func loginToDockerHub(HarborFullHost string) {

	if HarborFullHost == "" {
		HarborFullHost = "https://registry-1.docker.io"
	}

	login, err := apiClient.RegistryLogin(context.TODO(), types.AuthConfig{
		Username:      "icederce",
		Password:      "loveff.cxc.23",
		Auth:          "aWNlZGVyY2U6bG92ZWZmLmN4Yy4yMw==",
		ServerAddress: HarborFullHost,
	})
	if err != nil {
		log.ErrorF("[loginToDockerHub] - login to %s failed !", HarborFullHost)
	}

	log.DebugF("[loginToDockerHub] - login is %s", login.Status)
}

func WriteDependencyImageToFile() {
	imageFilePrefix := "C:\\Users\\wddsh\\Documents\\IdeaProjects\\ProjectOctopus\\cmii_operator\\image\\"

	middleFile := imageFilePrefix + "middle-image.txt"
	_ = os.Remove(middleFile)
	for _, image := range d_app.MiddlewareAmd64 {
		utils.AppendContentToFile(image+"\n", middleFile)
	}

	rkeFile := imageFilePrefix + "rke-image.txt"
	_ = os.Remove(rkeFile)
	for _, image := range d_app.Rancher1204Amd64 {
		utils.AppendContentToFile(image+"\n", rkeFile)
	}
}
