//go:build unix

// Package nfsmount implements mounting functionality using serve nfs command
//
// This can potentially work on all unix like systems which can mount NFS.
package nfsmount

import (
	"bytes"
	"context"
	"fmt"
	"net"
	"os/exec"
	"runtime"

	"github.com/rclone/rclone/cmd/mountlib"
	"github.com/rclone/rclone/cmd/serve/nfs"
	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/config/flags"
	"github.com/rclone/rclone/vfs"
)

var (
	sudo         = false
	nfsServerOpt nfs.Options
)

func init() {
	name := "nfsmount"
	cmd := mountlib.NewMountCommand(name, false, mount)
	cmd.Annotations["versionIntroduced"] = "v1.65"
	cmd.Annotations["status"] = "Experimental"
	mountlib.AddRc(name, mount)
	cmdFlags := cmd.Flags()
	flags.BoolVarP(cmdFlags, &sudo, "sudo", "", sudo, "Use sudo to run the mount/umount commands as root.", "")
	nfs.AddFlags(cmdFlags, &nfsServerOpt)
}

func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (asyncerrors <-chan error, unmount func() error, err error) {
	s, err := nfs.NewServer(context.Background(), VFS, &nfsServerOpt)
	if err != nil {
		return
	}
	errChan := make(chan error, 1)
	go func() {
		errChan <- s.Serve()
	}()
	// The port is always picked at random after the NFS server has started
	// we need to query the server for the port number so we can mount it
	_, port, err := net.SplitHostPort(s.Addr().String())
	if err != nil {
		err = fmt.Errorf("cannot find port number in %s", s.Addr().String())
		return
	}

	// Options
	options := []string{
		"-o", fmt.Sprintf("port=%s", port),
		"-o", fmt.Sprintf("mountport=%s", port),
		"-o", "tcp",
	}
	for _, option := range opt.ExtraOptions {
		options = append(options, "-o", option)
	}
	options = append(options, opt.ExtraFlags...)

	cmd := []string{}
	if sudo {
		cmd = append(cmd, "sudo")
	}
	cmd = append(cmd, "mount")
	cmd = append(cmd, options...)
	cmd = append(cmd, "localhost:"+opt.VolumeName, mountpoint)
	fs.Debugf(nil, "Running mount command: %q", cmd)

	out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
	if err != nil {
		out = bytes.TrimSpace(out)
		err = fmt.Errorf("%s: failed to mount NFS volume: %v", out, err)
		return
	}
	asyncerrors = errChan
	unmount = func() error {
		if s.UnmountedExternally {
			return nil
		}
		var umountErr error
		var out []byte
		if runtime.GOOS == "darwin" {
			out, umountErr = exec.Command("diskutil", "umount", "force", mountpoint).CombinedOutput()
		} else {
			cmd := []string{}
			if sudo {
				cmd = append(cmd, "sudo")
			}
			cmd = append(cmd, "umount", "-f", mountpoint)
			out, umountErr = exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
		}
		shutdownErr := s.Shutdown()
		VFS.Shutdown()
		if umountErr != nil {
			out = bytes.TrimSpace(out)
			return fmt.Errorf("%s: failed to umount the NFS volume %e", out, umountErr)
		} else if shutdownErr != nil {
			return fmt.Errorf("failed to shutdown NFS server: %e", shutdownErr)
		}
		return nil
	}

	nfs.OnUnmountFunc = func() {
		s.UnmountedExternally = true
		errChan <- nil
		VFS.Shutdown()
	}

	return
}
