package httpd

import (
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/LeeEirc/elfinder"

	"github.com/jumpserver/koko/pkg/common"
	"github.com/jumpserver/koko/pkg/config"
	"github.com/jumpserver/koko/pkg/logger"
	"github.com/jumpserver/koko/pkg/model"
	"github.com/jumpserver/koko/pkg/service"
	"github.com/jumpserver/koko/pkg/srvconn"
)

func NewUserVolume(user *model.User, addr, hostId string) *UserVolume {
	var assets []model.Asset
	homename := "Home"
	switch hostId {
	case "":
		assets = service.GetUserAssets(user.ID, "1", "")

	default:
		assets = service.GetUserAssets(user.ID, "1", hostId)
		if len(assets) == 1 {
			homename = assets[0].Hostname
			if assets[0].OrgID != "" {
				homename = fmt.Sprintf("%s.%s", assets[0].Hostname, assets[0].OrgName)
			}
		}
	}
	conf := config.GetConf()
	rawID := fmt.Sprintf("%s@%s", user.Username, addr)
	uVolume := &UserVolume{
		Uuid:         elfinder.GenerateID(rawID),
		UserSftp:     srvconn.NewUserSFTP(user, addr, assets...),
		Homename:     homename,
		basePath:     filepath.Join("/", homename),
		localTmpPath: filepath.Join(conf.RootPath, "data", "tmp"),
	}
	return uVolume
}

type UserVolume struct {
	Uuid string
	*srvconn.UserSftp
	localTmpPath string
	Homename     string
	basePath     string
}

func (u *UserVolume) ID() string {
	return u.Uuid
}

func (u *UserVolume) Info(path string) (elfinder.FileDir, error) {
	logger.Debug("volume Info: ", path)

	if path == "/" {
		return u.RootFileDir(), nil
	}

	var rest elfinder.FileDir
	originFileInfo, err := u.Stat(path)
	if err != nil {
		return rest, err
	}
	dirPath := filepath.Dir(path)
	filename := filepath.Base(path)
	rest.Read, rest.Write = elfinder.ReadWritePem(originFileInfo.Mode())
	if filename != originFileInfo.Name() {
		rest.Read, rest.Write = 1, 1
	}
	if filename == "." {
		filename = originFileInfo.Name()
	}
	rest.Name = filename
	rest.Hash = hashPath(u.Uuid, filepath.Join(dirPath, filename))
	rest.Phash = hashPath(u.Uuid, dirPath)
	if rest.Hash == rest.Phash {
		rest.Phash = ""
	}
	rest.Size = originFileInfo.Size()
	rest.Volumeid = u.Uuid
	if originFileInfo.IsDir() {
		rest.Mime = "directory"
		rest.Dirs = 1
	} else {
		rest.Mime = "file"
		rest.Dirs = 0
	}
	return rest, err
}

func (u *UserVolume) List(path string) []elfinder.FileDir {
	dirs := make([]elfinder.FileDir, 0)
	logger.Debug("volume List: ", path)
	dirInfo, err := u.Info(path)
	if err != nil {
		return dirs
	}
	dirs = append(dirs, dirInfo)
	originFileInfolist, err := u.UserSftp.ReadDir(path)
	if err != nil {
		return dirs
	}
	for i := 0; i < len(originFileInfolist); i++ {
		dirs = append(dirs, NewElfinderFileInfo(u.Uuid, path, originFileInfolist[i]))
	}
	return dirs
}

func (u *UserVolume) Parents(path string, dep int) []elfinder.FileDir {
	logger.Debug("volume Parents: ", path)
	dirs := make([]elfinder.FileDir, 0)
	dirPath := path
	for {
		tmps := u.List(dirPath)
		dirs = append(dirs, tmps...)
		if dirPath == "/" {
			break
		}
		dirPath = filepath.Dir(dirPath)
	}
	return dirs
}

func (u *UserVolume) GetFile(path string) (reader io.ReadCloser, err error) {
	return u.UserSftp.Open(path)
}

func (u *UserVolume) UploadFile(dir, filename string, reader io.Reader) (elfinder.FileDir, error) {
	path := filepath.Join(dir, filename)
	logger.Debug("Volume upload file path: ", path)
	var rest elfinder.FileDir
	fd, err := u.UserSftp.Create(path)
	if err != nil {
		return rest, err
	}
	defer fd.Close()

	_, err = io.Copy(fd, reader)
	if err != nil {
		return rest, err
	}
	return u.Info(path)
}

func (u *UserVolume) UploadChunk(cid int, dirPath, chunkName string, reader io.Reader) error {
	//chunkName format "filename.[NUMBER]_[TOTAL].part"
	var err error
	tmpDir := filepath.Join(u.localTmpPath, dirPath)
	err = common.EnsureDirExist(tmpDir)
	if err != nil {
		return err
	}
	chunkRealPath := fmt.Sprintf("%s_%d",
		filepath.Join(tmpDir, chunkName), cid)

	fd, err := os.Create(chunkRealPath)
	defer fd.Close()
	if err != nil {
		return err
	}
	_, err = io.Copy(fd, reader)
	return err
}

func (u *UserVolume) MergeChunk(cid, total int, dirPath, filename string) (elfinder.FileDir, error) {
	path := filepath.Join(dirPath, filename)
	logger.Debug("merge chunk path: ", path)
	var rest elfinder.FileDir
	fd, err := u.UserSftp.Create(path)
	if err != nil {
		for i := 0; i <= total; i++ {
			partPath := fmt.Sprintf("%s.%d_%d.part_%d",
				filepath.Join(u.localTmpPath, dirPath, filename), i, total, cid)
			_ = os.Remove(partPath)
		}
		return rest, err
	}
	defer fd.Close()

	for i := 0; i <= total; i++ {
		partPath := fmt.Sprintf("%s.%d_%d.part_%d",
			filepath.Join(u.localTmpPath, dirPath, filename), i, total, cid)

		partFD, err := os.Open(partPath)
		if err != nil {
			logger.Debug(err)
			_ = os.Remove(partPath)
			continue
		}
		_, err = io.Copy(fd, partFD)
		if err != nil {
			return rest, os.ErrNotExist
		}
		_ = partFD.Close()
		_ = os.Remove(partPath)
	}
	return u.Info(path)
}

func (u *UserVolume) CompleteChunk(cid, total int, dirPath, filename string) bool {
	for i := 0; i <= total; i++ {
		partPath := fmt.Sprintf("%s.%d_%d.part_%d",
			filepath.Join(u.localTmpPath, dirPath, filename), i, total, cid)
		_, err := os.Stat(partPath)
		if err != nil {
			return false
		}
	}
	return true
}

func (u *UserVolume) MakeDir(dir, newDirname string) (elfinder.FileDir, error) {
	path := filepath.Join(dir, newDirname)
	var rest elfinder.FileDir
	err := u.UserSftp.MkdirAll(path)
	if err != nil {
		return rest, err
	}
	return u.Info(path)
}

func (u *UserVolume) MakeFile(dir, newFilename string) (elfinder.FileDir, error) {
	path := filepath.Join(dir, newFilename)
	var rest elfinder.FileDir
	fd, err := u.UserSftp.Create(path)
	if err != nil {
		return rest, err
	}
	defer fd.Close()
	return u.Info(path)
}

func (u *UserVolume) Rename(oldNamePath, newName string) (elfinder.FileDir, error) {
	var rest elfinder.FileDir
	newNamePath := filepath.Join(filepath.Dir(oldNamePath), newName)
	err := u.UserSftp.Rename(oldNamePath, newNamePath)
	if err != nil {
		return rest, err
	}
	return u.Info(newNamePath)
}

func (u *UserVolume) Remove(path string) error {
	var res os.FileInfo
	var err error
	res, err = u.UserSftp.Stat(path)
	if err != nil {
		return err
	}
	if res.IsDir() {
		return u.UserSftp.RemoveDirectory(path)
	}
	return u.UserSftp.Remove(path)
}

func (u *UserVolume) Paste(dir, filename, suffix string, reader io.ReadCloser) (elfinder.FileDir, error) {
	var rest elfinder.FileDir
	path := filepath.Join(dir, filename)
	rest, err := u.Info(path)
	if err != nil {
		path += suffix
	}
	fd, err := u.UserSftp.Create(path)
	if err != nil {
		return rest, err
	}
	defer fd.Close()
	_, err = io.Copy(fd, reader)
	if err != nil {
		return rest, err
	}
	return u.Info(path)
}

func (u *UserVolume) RootFileDir() elfinder.FileDir {
	logger.Debug("Root File Dir")
	fInfo, _ := u.UserSftp.Info()
	var rest elfinder.FileDir
	rest.Name = u.Homename
	rest.Hash = hashPath(u.Uuid, "/")
	rest.Size = fInfo.Size()
	rest.Volumeid = u.Uuid
	rest.Mime = "directory"
	rest.Dirs = 1
	rest.Read, rest.Write = 1, 1
	rest.Locked = 1
	return rest
}

func (u *UserVolume) Close() {
	u.UserSftp.Close()
}

func NewElfinderFileInfo(id, dirPath string, originFileInfo os.FileInfo) elfinder.FileDir {
	var rest elfinder.FileDir
	rest.Name = originFileInfo.Name()
	rest.Hash = hashPath(id, filepath.Join(dirPath, originFileInfo.Name()))
	rest.Phash = hashPath(id, dirPath)
	if rest.Hash == rest.Phash {
		rest.Phash = ""
	}
	rest.Size = originFileInfo.Size()
	rest.Volumeid = id
	if originFileInfo.IsDir() {
		rest.Mime = "directory"
		rest.Dirs = 1
	} else {
		rest.Mime = "file"
		rest.Dirs = 0
	}
	rest.Read, rest.Write = elfinder.ReadWritePem(originFileInfo.Mode())
	return rest
}

func hashPath(id, path string) string {
	return elfinder.CreateHash(id, path)
}