package mount

import (
	"context"
	"os"
	"syscall"
	"time"

	"github.com/seaweedfs/go-fuse/v2/fuse"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
)

/** Create a symbolic link */
func (wfs *WFS) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target string, name string, out *fuse.EntryOut) (code fuse.Status) {

	if wfs.IsOverQuotaWithUncommitted() {
		return fuse.Status(syscall.ENOSPC)
	}
	var s fuse.Status
	if name, s = checkName(name); s != fuse.OK {
		return s
	}

	dirPath, code := wfs.inodeToPath.GetPath(header.NodeId)
	if code != fuse.OK {
		return
	}
	entryFullPath := dirPath.Child(name)

	now := time.Now().Unix()
	// Pre-allocate the mount's local inode so the filer stores the same
	// object identity we report to the kernel below. Without this, the filer
	// assigns its own inode and subsequent cached reads would disagree with
	// the inode we return from Symlink.
	inode := wfs.inodeToPath.AllocateInode(entryFullPath, now)
	request := &filer_pb.CreateEntryRequest{
		Directory: string(dirPath),
		Entry: &filer_pb.Entry{
			Name:        name,
			IsDirectory: false,
			Attributes: &filer_pb.FuseAttributes{
				Mtime:         now,
				Crtime:        now,
				Ctime:         now,
				FileMode:      uint32(os.FileMode(0777) | os.ModeSymlink),
				Uid:           header.Uid,
				Gid:           header.Gid,
				SymlinkTarget: target,
				Inode:         inode,
			},
		},
		Signatures:               []int32{wfs.signature},
		SkipCheckParentDirectory: true,
	}

	wfs.mapPbIdFromLocalToFiler(request.Entry)

	resp, err := wfs.streamCreateEntry(context.Background(), request)
	if err == nil {
		event := resp.GetMetadataEvent()
		if event == nil {
			event = metadataCreateEvent(string(dirPath), request.Entry)
		}
		if applyErr := wfs.applyLocalMetadataEvent(context.Background(), event); applyErr != nil {
			glog.Warningf("symlink %s: best-effort metadata apply failed: %v", entryFullPath, applyErr)
			wfs.inodeToPath.InvalidateChildrenCache(dirPath)
		}
		wfs.touchDirMtimeCtimeBest(dirPath)
	}

	// Map back to local uid/gid before writing to the kernel.
	wfs.mapPbIdFromFilerToLocal(request.Entry)

	if err != nil {
		glog.V(0).Infof("Symlink %s => %s: %v", entryFullPath, target, err)
		return fuse.EIO
	}

	inode = wfs.inodeToPath.Lookup(entryFullPath, request.Entry.Attributes.Crtime, false, false, inode, true)

	wfs.outputPbEntry(out, inode, request.Entry)

	return fuse.OK
}

func (wfs *WFS) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, code fuse.Status) {
	entryFullPath, code := wfs.inodeToPath.GetPath(header.NodeId)
	if code != fuse.OK {
		return
	}

	entry, _, status := wfs.maybeLoadEntry(entryFullPath)
	if status != fuse.OK {
		return nil, status
	}
	if os.FileMode(entry.Attributes.FileMode)&os.ModeSymlink == 0 {
		return nil, fuse.EINVAL
	}

	return []byte(entry.Attributes.SymlinkTarget), fuse.OK
}
