package api

import (
	"fmt"
	"io"
	"io/fs"
	"mayfly-go/internal/machine/api/form"
	"mayfly-go/internal/machine/api/vo"
	"mayfly-go/internal/machine/application"
	"mayfly-go/internal/machine/domain/entity"
	"mayfly-go/internal/machine/infrastructure/machine"
	msgapp "mayfly-go/internal/msg/application"
	"mayfly-go/pkg/biz"
	"mayfly-go/pkg/ginx"
	"mayfly-go/pkg/logx"
	"mayfly-go/pkg/req"
	"mayfly-go/pkg/utils/collx"
	"mayfly-go/pkg/utils/jsonx"
	"mayfly-go/pkg/utils/timex"
	"mayfly-go/pkg/ws"
	"mime/multipart"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/gin-gonic/gin"
)

type MachineFile struct {
	MachineFileApp application.MachineFile
	MsgApp         msgapp.Msg
}

const (
	file          = "-"
	dir           = "d"
	link          = "l"
	max_read_size = 1 * 1024 * 1024
)

func (m *MachineFile) MachineFiles(rc *req.Ctx) {
	g := rc.GinCtx
	condition := &entity.MachineFile{MachineId: GetMachineId(g)}
	rc.ResData = m.MachineFileApp.GetPageList(condition, ginx.GetPageParam(g), new([]vo.MachineFileVO))
}

func (m *MachineFile) SaveMachineFiles(rc *req.Ctx) {
	fileForm := new(form.MachineFileForm)
	entity := ginx.BindJsonAndCopyTo[*entity.MachineFile](rc.GinCtx, fileForm, new(entity.MachineFile))
	entity.SetBaseInfo(rc.LoginAccount)

	rc.ReqParam = fileForm
	m.MachineFileApp.Save(entity)
}

func (m *MachineFile) DeleteFile(rc *req.Ctx) {
	m.MachineFileApp.Delete(GetMachineFileId(rc.GinCtx))
}

/***      sftp相关操作      */

func (m *MachineFile) CreateFile(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	form := ginx.BindJsonAndValid(g, new(form.MachineCreateFileForm))
	path := form.Path

	attrs := jsonx.Kvs("path", path)
	var mi *machine.Info
	var err error
	if form.Type == dir {
		attrs["type"] = "目录"
		mi, err = m.MachineFileApp.MkDir(fid, form.Path)
	} else {
		attrs["type"] = "文件"
		mi, err = m.MachineFileApp.CreateFile(fid, form.Path)
	}
	attrs["machine"] = mi
	rc.ReqParam = attrs
	biz.ErrIsNilAppendErr(err, "创建目录失败: %s")
}

func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)
	readPath := g.Query("path")
	readType := g.Query("type")

	sftpFile, mi, err := m.MachineFileApp.ReadFile(fid, readPath)
	rc.ReqParam = jsonx.Kvs("machine", mi, "path", readPath)
	biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
	defer sftpFile.Close()

	fileInfo, _ := sftpFile.Stat()
	filesize := fileInfo.Size()
	// 如果是读取文件内容，则校验文件大小
	if readType != "1" {
		biz.IsTrue(filesize < max_read_size, "文件超过1m，请使用下载查看")
	}
	// 如果读取类型为下载，则下载文件，否则获取文件内容
	if readType == "1" {
		// 截取文件名，如/usr/local/test.java -》 test.java
		path := strings.Split(readPath, "/")
		rc.Download(sftpFile, path[len(path)-1])
	} else {
		datas, err := io.ReadAll(sftpFile)
		biz.ErrIsNilAppendErr(err, "读取文件内容失败: %s")
		rc.ResData = string(datas)
	}
}

func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)
	readPath := g.Query("path")
	rc.ReqParam = fmt.Sprintf("path: %s", readPath)

	if !strings.HasSuffix(readPath, "/") {
		readPath = readPath + "/"
	}
	fis := m.MachineFileApp.ReadDir(fid, readPath)
	fisVO := make([]vo.MachineFileInfo, 0)
	for _, fi := range fis {
		fisVO = append(fisVO, vo.MachineFileInfo{
			Name:    fi.Name(),
			Size:    fi.Size(),
			Path:    readPath + fi.Name(),
			Type:    getFileType(fi.Mode()),
			Mode:    fi.Mode().String(),
			ModTime: timex.DefaultFormat(fi.ModTime()),
		})

	}
	sort.Sort(vo.MachineFileInfos(fisVO))
	rc.ResData = fisVO
}

func (m *MachineFile) GetDirSize(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)
	readPath := g.Query("path")

	rc.ResData = m.MachineFileApp.GetDirSize(fid, readPath)
}

func (m *MachineFile) GetFileStat(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)
	readPath := g.Query("path")

	rc.ResData = m.MachineFileApp.FileStat(fid, readPath)
}

func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	form := new(form.MachineFileUpdateForm)
	ginx.BindJsonAndValid(g, form)
	path := form.Path

	mi, err := m.MachineFileApp.WriteFileContent(fid, path, []byte(form.Content))
	rc.ReqParam = jsonx.Kvs("machine", mi, "path", path)
	biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
}

const MaxUploadFileSize int64 = 1024 * 1024 * 1024

func (m *MachineFile) UploadFile(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)
	path := g.PostForm("path")

	fileheader, err := g.FormFile("file")
	biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
	biz.IsTrue(fileheader.Size <= MaxUploadFileSize, "文件大小不能超过%d字节", MaxUploadFileSize)

	file, _ := fileheader.Open()
	defer file.Close()

	la := rc.LoginAccount
	defer func() {
		if err := recover(); err != nil {
			logx.Errorf("文件上传失败: %s", err)
			switch t := err.(type) {
			case biz.BizError:
				m.MsgApp.CreateAndSend(la, ws.ErrSysMsg("文件上传失败", fmt.Sprintf("执行文件上传失败：\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())))
			}
		}
	}()

	mi, err := m.MachineFileApp.UploadFile(fid, path, fileheader.Filename, file)
	rc.ReqParam = jsonx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
	biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
	// 保存消息并发送文件上传成功通知
	m.MsgApp.CreateAndSend(la, ws.SuccessSysMsg("文件上传成功", fmt.Sprintf("[%s]文件已成功上传至 %s[%s:%s]", fileheader.Filename, mi.Name, mi.Ip, path)))
}

type FolderFile struct {
	Dir        string
	Fileheader *multipart.FileHeader
}

func (m *MachineFile) UploadFolder(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	mf, err := g.MultipartForm()
	biz.ErrIsNilAppendErr(err, "获取表单信息失败: %s")
	basePath := mf.Value["basePath"][0]
	biz.NotEmpty(basePath, "基础路径不能为空")

	fileheaders := mf.File["files"]
	biz.IsTrue(len(fileheaders) > 0, "文件不能为空")
	allFileSize := collx.ArrayReduce(fileheaders, 0, func(i int64, fh *multipart.FileHeader) int64 {
		return i + fh.Size
	})
	biz.IsTrue(allFileSize <= MaxUploadFileSize, "文件夹总大小不能超过%d字节", MaxUploadFileSize)

	paths := mf.Value["paths"]

	folderName := filepath.Dir(paths[0])
	mcli := m.MachineFileApp.GetMachineCli(fid, basePath+"/"+folderName)
	mi := mcli.GetMachine()
	sftpCli := mcli.GetSftpCli()
	rc.ReqParam = jsonx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", basePath, folderName))

	folderFiles := make([]FolderFile, len(paths))
	// 先创建目录，并将其包装为folderFile结构
	mkdirs := make(map[string]bool, 0)
	for i, path := range paths {
		dir := filepath.Dir(path)
		// 目录已建，则无需重复建
		if !mkdirs[dir] {
			biz.ErrIsNilAppendErr(sftpCli.MkdirAll(basePath+"/"+dir), "创建目录失败: %s")
			mkdirs[dir] = true
		}
		folderFiles[i] = FolderFile{
			Dir:        dir,
			Fileheader: fileheaders[i],
		}
	}

	// 分组处理
	groupNum := 30
	chunks := collx.ArraySplit(folderFiles, groupNum)

	var wg sync.WaitGroup
	// 设置要等待的协程数量
	wg.Add(len(chunks))

	la := rc.LoginAccount
	for _, chunk := range chunks {
		go func(files []FolderFile, wg *sync.WaitGroup) {
			defer func() {
				// 协程执行完成后调用Done方法
				wg.Done()
				if err := recover(); err != nil {
					logx.Errorf("文件上传失败: %s", err)
					switch t := err.(type) {
					case biz.BizError:
						m.MsgApp.CreateAndSend(la, ws.ErrSysMsg("文件上传失败", fmt.Sprintf("执行文件上传失败：\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())))
					}
				}
			}()

			for _, file := range files {
				fileHeader := file.Fileheader
				dir := file.Dir
				file, _ := fileHeader.Open()
				defer file.Close()

				logx.Debugf("上传文件夹: dir=%s -> filename=%s", dir, fileHeader.Filename)

				createfile, err := sftpCli.Create(fmt.Sprintf("%s/%s/%s", basePath, dir, fileHeader.Filename))
				biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
				defer createfile.Close()
				io.Copy(createfile, file)
			}
		}(chunk, &wg)
	}

	// 等待所有协程执行完成
	wg.Wait()
	// 保存消息并发送文件上传成功通知
	m.MsgApp.CreateAndSend(rc.LoginAccount, ws.SuccessSysMsg("文件上传成功", fmt.Sprintf("[%s]文件夹已成功上传至 %s[%s:%s]", folderName, mi.Name, mi.Ip, basePath)))
}

func (m *MachineFile) RemoveFile(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	rmForm := new(form.MachineFileOpForm)
	ginx.BindJsonAndValid(g, rmForm)

	mi, err := m.MachineFileApp.RemoveFile(fid, rmForm.Path...)
	rc.ReqParam = jsonx.Kvs("machine", mi, "path", rmForm.Path)
	biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
}

func (m *MachineFile) CopyFile(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	cpForm := new(form.MachineFileOpForm)
	ginx.BindJsonAndValid(g, cpForm)
	mi, err := m.MachineFileApp.Copy(fid, cpForm.ToPath, cpForm.Path...)
	biz.ErrIsNilAppendErr(err, "文件拷贝失败: %s")
	rc.ReqParam = jsonx.Kvs("machine", mi, "cp", cpForm)
}

func (m *MachineFile) MvFile(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	cpForm := new(form.MachineFileOpForm)
	ginx.BindJsonAndValid(g, cpForm)
	mi, err := m.MachineFileApp.Mv(fid, cpForm.ToPath, cpForm.Path...)
	rc.ReqParam = jsonx.Kvs("machine", mi, "mv", cpForm)
	biz.ErrIsNilAppendErr(err, "文件移动失败: %s")
}

func (m *MachineFile) Rename(rc *req.Ctx) {
	g := rc.GinCtx
	fid := GetMachineFileId(g)

	rename := new(form.MachineFileRename)
	ginx.BindJsonAndValid(g, rename)
	mi, err := m.MachineFileApp.Rename(fid, rename.Oldname, rename.Newname)
	rc.ReqParam = jsonx.Kvs("machine", mi, "rename", rename)
	biz.ErrIsNilAppendErr(err, "文件重命名失败: %s")
}

func getFileType(fm fs.FileMode) string {
	if fm.IsDir() {
		return dir
	}
	if fm.IsRegular() {
		return file
	}
	return dir
}

func GetMachineFileId(g *gin.Context) uint64 {
	fileId, _ := strconv.Atoi(g.Param("fileId"))
	biz.IsTrue(fileId != 0, "fileId错误")
	return uint64(fileId)
}
