//go:build !plan9
// +build !plan9

package sftp

import (
	"io"
	"os"
	"syscall"
	"time"

	"github.com/pkg/sftp"
	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/vfs"
)

// vfsHandler converts the VFS to be served by SFTP
type vfsHandler struct {
	*vfs.VFS
}

// vfsHandler returns a Handlers object with the test handlers.
func newVFSHandler(vfs *vfs.VFS) sftp.Handlers {
	v := vfsHandler{VFS: vfs}
	return sftp.Handlers{
		FileGet:  v,
		FilePut:  v,
		FileCmd:  v,
		FileList: v,
	}
}

func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
	file, err := v.OpenFile(r.Filepath, os.O_RDONLY, 0777)
	if err != nil {
		return nil, err
	}
	return file, nil
}

func (v vfsHandler) Filewrite(r *sftp.Request) (io.WriterAt, error) {
	file, err := v.OpenFile(r.Filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
	if err != nil {
		return nil, err
	}
	return file, nil
}

func (v vfsHandler) Filecmd(r *sftp.Request) error {
	switch r.Method {
	case "Setstat":
		attr := r.Attributes()
		if attr.Mtime != 0 {
			modTime := time.Unix(int64(attr.Mtime), 0)
			err := v.Chtimes(r.Filepath, modTime, modTime)
			if err != nil {
				return err
			}
		}
		return nil
	case "Rename":
		err := v.Rename(r.Filepath, r.Target)
		if err != nil {
			return err
		}
	case "Rmdir", "Remove":
		err := v.Remove(r.Filepath)
		if err != nil {
			return err
		}
	case "Mkdir":
		err := v.Mkdir(r.Filepath, 0777)
		if err != nil {
			return err
		}
	case "Symlink":
		// FIXME
		// _, err := v.fetch(r.Filepath)
		// if err != nil {
		// 	return err
		// }
		// link := newMemFile(r.Target, false)
		// link.symlink = r.Filepath
		// v.files[r.Target] = link
		return sftp.ErrSshFxOpUnsupported
	}
	return nil
}

type listerat []os.FileInfo

// Modeled after strings.Reader's ReadAt() implementation
func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
	var n int
	if offset >= int64(len(f)) {
		return 0, io.EOF
	}
	n = copy(ls, f[offset:])
	if n < len(ls) {
		return n, io.EOF
	}
	return n, nil
}

func (v vfsHandler) Filelist(r *sftp.Request) (l sftp.ListerAt, err error) {
	var node vfs.Node
	var handle vfs.Handle
	switch r.Method {
	case "List":
		node, err = v.Stat(r.Filepath)
		if err != nil {
			return nil, err
		}
		if !node.IsDir() {
			return nil, syscall.ENOTDIR
		}
		handle, err = node.Open(os.O_RDONLY)
		if err != nil {
			return nil, err
		}
		defer fs.CheckClose(handle, &err)
		fis, err := handle.Readdir(-1)
		if err != nil {
			return nil, err
		}
		return listerat(fis), nil
	case "Stat":
		node, err = v.Stat(r.Filepath)
		if err != nil {
			return nil, err
		}
		return listerat([]os.FileInfo{node}), nil
	case "Readlink":
		// FIXME
		// if file.symlink != "" {
		// 	file, err = v.fetch(file.symlink)
		// 	if err != nil {
		// 		return nil, err
		// 	}
		// }
		// return listerat([]os.FileInfo{file}), nil
	}
	return nil, sftp.ErrSshFxOpUnsupported
}
