package application

import (
	"io"
	"io/fs"
	"io/ioutil"
	"mayfly-go/base/biz"
	"mayfly-go/base/model"
	"mayfly-go/server/devops/domain/entity"
	"mayfly-go/server/devops/domain/repository"
	"mayfly-go/server/devops/infrastructure/persistence"
	"os"
	"strings"

	"github.com/pkg/sftp"
)

type IMachineFile interface {
	// 分页获取机器文件信息列表
	GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult

	// 根据条件获取
	GetMachineFile(condition *entity.MachineFile, cols ...string) error

	// 根据id获取
	GetById(id uint64, cols ...string) *entity.MachineFile

	Save(entity *entity.MachineFile)

	Delete(id uint64)

	/**  sftp 相关操作 **/

	// 读取目录
	ReadDir(fid uint64, path string) []fs.FileInfo

	// 读取文件内容
	ReadFile(fileId uint64, path string) ([]byte, fs.FileInfo)

	// 写文件
	WriteFileContent(fileId uint64, path string, content []byte)

	// 文件上传
	UploadFile(fileId uint64, path, filename string, content []byte)

	// 移除文件
	RemoveFile(fileId uint64, path string)
}

type machineFileApp struct {
	machineFileRepo repository.MachineFile
	machineRepo     repository.Machine
}

// 实现类单例
var MachineFile IMachineFile = &machineFileApp{
	machineRepo:     persistence.MachineDao,
	machineFileRepo: persistence.MachineFileDao,
}

// 分页获取机器脚本信息列表
func (m *machineFileApp) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
	return m.machineFileRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}

// 根据条件获取
func (m *machineFileApp) GetMachineFile(condition *entity.MachineFile, cols ...string) error {
	return m.machineFileRepo.GetMachineFile(condition, cols...)
}

// 根据id获取
func (m *machineFileApp) GetById(id uint64, cols ...string) *entity.MachineFile {
	return m.machineFileRepo.GetById(id, cols...)
}

// 保存机器文件配置
func (m *machineFileApp) Save(entity *entity.MachineFile) {
	biz.NotNil(m.machineRepo.GetById(entity.MachineId, "Name"), "该机器不存在")

	if entity.Id != 0 {
		model.UpdateById(entity)
	} else {
		model.Insert(entity)
	}
}

// 根据id删除
func (m *machineFileApp) Delete(id uint64) {
	m.machineFileRepo.Delete(id)
}

func (m *machineFileApp) ReadDir(fid uint64, path string) []fs.FileInfo {
	path, machineId := m.checkAndReturnPathMid(fid, path)
	if !strings.HasSuffix(path, "/") {
		path = path + "/"
	}

	sftpCli := m.getSftpCli(machineId)
	fis, err := sftpCli.ReadDir(path)
	biz.ErrIsNilAppendErr(err, "读取目录失败: %s")
	return fis
}

func (m *machineFileApp) ReadFile(fileId uint64, path string) ([]byte, fs.FileInfo) {
	path, machineId := m.checkAndReturnPathMid(fileId, path)
	sftpCli := m.getSftpCli(machineId)
	// 读取文件内容
	fc, err := sftpCli.Open(path)
	biz.ErrIsNilAppendErr(err, "打开文件失败：%s")
	defer fc.Close()

	fileInfo, _ := fc.Stat()
	biz.IsTrue(!fileInfo.IsDir(), "该文件为目录")
	dataByte, err := ioutil.ReadAll(fc)
	if err != nil && err != io.EOF {
		panic(biz.NewBizErr("读取文件内容失败"))
	}
	return dataByte, fileInfo
}

// 写文件内容
func (m *machineFileApp) WriteFileContent(fileId uint64, path string, content []byte) {
	_, machineId := m.checkAndReturnPathMid(fileId, path)

	sftpCli := m.getSftpCli(machineId)
	f, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE|os.O_RDWR)
	biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
	defer f.Close()

	fi, _ := f.Stat()
	biz.IsTrue(!fi.IsDir(), "该路径不是文件")
	f.Write(content)
}

// 上传文件
func (m *machineFileApp) UploadFile(fileId uint64, path, filename string, content []byte) {
	path, machineId := m.checkAndReturnPathMid(fileId, path)
	if !strings.HasSuffix(path, "/") {
		path = path + "/"
	}

	sftpCli := m.getSftpCli(machineId)
	createfile, err := sftpCli.Create(path + filename)
	biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
	defer createfile.Close()

	createfile.Write(content)
}

// 删除文件
func (m *machineFileApp) RemoveFile(fileId uint64, path string) {
	path, machineId := m.checkAndReturnPathMid(fileId, path)

	sftpCli := m.getSftpCli(machineId)
	file, err := sftpCli.Open(path)
	biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
	fi, err := file.Stat()
	if fi.IsDir() {
		err = sftpCli.RemoveDirectory(path)
	} else {
		err = sftpCli.Remove(path)
	}
	biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
}

// 获取sftp client
func (m *machineFileApp) getSftpCli(machineId uint64) *sftp.Client {
	return Machine.GetCli(machineId).GetSftpCli()
}

// 校验并返回实际可访问的文件path
func (m *machineFileApp) checkAndReturnPathMid(fid uint64, inputPath string) (string, uint64) {
	biz.IsTrue(fid != 0, "文件id不能为空")
	mf := m.GetById(uint64(fid))
	biz.NotNil(mf, "文件不存在")
	if inputPath != "" {
		// 接口传入的地址需为配置路径的子路径
		biz.IsTrue(strings.HasPrefix(inputPath, mf.Path), "无权访问该目录或文件")
		return inputPath, mf.MachineId
	} else {
		return mf.Path, mf.MachineId
	}
}
