package vfs

import (
	"context"
	"strings"
	"time"

	"gitlink.org.cn/cloudream/common/utils/lo2"
	"gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
	"gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/fuse"
	"gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/vfs/cache"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gorm.io/gorm"
)

type FuseNode interface {
	PathComps() []string
}

func child(vfs *Vfs, ctx context.Context, parent FuseNode, name string) (fuse.FsEntry, error) {
	parentPathComps := parent.PathComps()

	childPathComps := lo2.AppendNew(parentPathComps, name)
	ca := vfs.cache.Stat(childPathComps)
	if ca == nil {
		var ret fuse.FsEntry

		d := vfs.db
		err := d.DoTx(func(tx db.SQLContext) error {
			pkg, err := d.Package().GetByFullName(tx, childPathComps[0], childPathComps[1])
			if err != nil {
				if err != gorm.ErrRecordNotFound {
					return err
				}
				return nil
			}

			objPath := clitypes.JoinObjectPath(childPathComps[2:]...)
			obj, err := d.Object().GetByPath(tx, pkg.PackageID, objPath)
			if err == nil {
				ret = newFileFromObject(vfs, childPathComps, obj)
				return nil
			}
			if err != gorm.ErrRecordNotFound {
				return err
			}

			err = d.Object().HasObjectWithPrefix(tx, pkg.PackageID, objPath+clitypes.ObjectPathSeparator)
			if err == nil {
				dir := vfs.cache.LoadDir(childPathComps, &cache.CreateDirOption{
					ModTime: time.Now(),
				})
				if dir == nil {
					return nil
				}

				ret = newDirFromCache(dir.Info(), vfs)
				return nil
			}

			if err == gorm.ErrRecordNotFound {
				return nil
			}

			return err
		})
		if err != nil {
			return nil, err
		}

		if ret == nil {
			return nil, fuse.ErrNotExists
		}

		return ret, nil
	}

	if ca.IsDir {
		return newDirFromCache(*ca, vfs), nil
	}

	return newFileFromCache(*ca, vfs), nil
}

func listChildren(vfs *Vfs, ctx context.Context, parent FuseNode) ([]fuse.FsEntry, error) {
	var ens []fuse.FsEntry

	myPathComps := parent.PathComps()

	infos := vfs.cache.StatMany(myPathComps)

	dbEntries := make(map[string]fuse.FsEntry)

	d := vfs.db
	d.DoTx(func(tx db.SQLContext) error {
		pkg, err := d.Package().GetByFullName(tx, myPathComps[0], myPathComps[1])
		if err != nil {
			return err
		}

		objPath := clitypes.JoinObjectPath(myPathComps[2:]...)
		objPrefix := objPath
		if objPath != "" {
			objPrefix += clitypes.ObjectPathSeparator
		}

		objs, coms, err := d.Object().GetByPrefixGrouped(tx, pkg.PackageID, objPrefix)
		if err != nil {
			return err
		}

		for _, dir := range coms {
			dir = strings.TrimSuffix(dir, clitypes.ObjectPathSeparator)
			pathComps := lo2.AppendNew(myPathComps, clitypes.BaseName(dir))

			cd := vfs.cache.LoadDir(pathComps, &cache.CreateDirOption{
				ModTime: time.Now(),
			})
			if cd == nil {
				continue
			}

			dbEntries[dir] = newDirFromCache(cd.Info(), vfs)
		}

		for _, obj := range objs {
			pathComps := lo2.AppendNew(myPathComps, clitypes.BaseName(obj.Path))
			file := newFileFromObject(vfs, pathComps, obj)
			dbEntries[file.Name()] = file
		}

		return nil
	})

	for _, c := range infos {
		delete(dbEntries, c.PathComps[len(c.PathComps)-1])

		if c.IsDir {
			ens = append(ens, newDirFromCache(c, vfs))
		} else {
			ens = append(ens, newFileFromCache(c, vfs))
		}
	}

	for _, e := range dbEntries {
		ens = append(ens, e)
	}

	return ens, nil
}

func newDir(vfs *Vfs, ctx context.Context, name string, parent FuseNode) (fuse.FsDir, error) {
	cache := vfs.cache.CreateDir(lo2.AppendNew(parent.PathComps(), name))
	if cache == nil {
		return nil, fuse.ErrPermission
	}

	return newDirFromCache(cache.Info(), vfs), nil
}

func newFile(vfs *Vfs, ctx context.Context, name string, parent FuseNode, flags uint32) (fuse.FileHandle, uint32, error) {
	ch := vfs.cache.CreateFile(lo2.AppendNew(parent.PathComps(), name))
	if ch == nil {
		return nil, 0, fuse.ErrPermission
	}
	defer ch.Release()

	if !ch.LevelUp(cache.LevelComplete) {
		return nil, 0, fuse.ErrIOError
	}

	// Open之后会给cache的引用计数额外+1，即使cache先于FileHandle被关闭，
	// 也有有FileHandle的计数保持cache的有效性

	fileNode := newFileFromCache(ch.Info(), vfs)
	hd := ch.Open(flags)
	return newFileHandle(fileNode, hd), flags, nil
}

func removeChild(vfs *Vfs, ctx context.Context, name string, parent FuseNode) error {
	pathComps := lo2.AppendNew(parent.PathComps(), name)
	joinedPath := clitypes.JoinObjectPath(pathComps[2:]...)
	d := vfs.db

	// TODO 生成系统事件
	return vfs.db.DoTx(func(tx db.SQLContext) error {
		pkg, err := d.Package().GetByFullName(tx, pathComps[0], pathComps[1])
		if err == nil {
			err := d.Object().HasObjectWithPrefix(tx, pkg.PackageID, joinedPath+clitypes.ObjectPathSeparator)
			if err == nil {
				return fuse.ErrNotEmpty
			}
			if err != gorm.ErrRecordNotFound {
				return err
			}

			// 存储系统不会保存目录结构，所以这里是尝试删除同名文件
			err = d.Object().DeleteCompleteByPath(tx, pkg.PackageID, joinedPath)
			if err != nil && err != gorm.ErrRecordNotFound {
				return err
			}

		} else if err != gorm.ErrRecordNotFound {
			return err
		}

		return vfs.cache.Remove(pathComps)
	})
}

func moveChild(vfs *Vfs, ctx context.Context, oldName string, oldParent FuseNode, newName string, newParent FuseNode) error {
	newParentPath := newParent.PathComps()
	newChildPath := lo2.AppendNew(newParentPath, newName)
	newChildPathJoined := clitypes.JoinObjectPath(newChildPath[2:]...)

	// 不允许移动任何内容到Package层级以上
	if len(newParentPath) < 2 {
		return fuse.ErrNotSupported
	}

	oldChildPath := lo2.AppendNew(oldParent.PathComps(), oldName)
	oldChildPathJoined := clitypes.JoinObjectPath(oldChildPath[2:]...)

	// 先更新远程，再更新本地，因为远程使用事务更新，可以回滚，而本地不行
	return vfs.db.DoTx(func(tx db.SQLContext) error {
		err := moveRemote(vfs, tx, oldChildPath, newParentPath, oldChildPathJoined, newChildPathJoined)
		if err == fuse.ErrExists {
			return err
		}
		if err != nil && err != fuse.ErrNotExists {
			return err
		}

		err2 := vfs.cache.Move(oldChildPath, newChildPath)
		if err2 == fuse.ErrNotExists {
			if err == fuse.ErrNotExists {
				return fuse.ErrNotExists
			}
			return nil
		}

		return err2
	})
}

func moveRemote(vfs *Vfs, tx db.SQLContext, oldChildPath []string, newParentPath []string, oldChildPathJoined string, newChildPathJoined string) error {
	d := vfs.db
	newPkg, err := d.Package().GetByFullName(tx, newParentPath[0], newParentPath[1])
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return fuse.ErrNotExists
		}
		return err
	}

	// 检查目的文件或文件夹是否已经存在
	_, err = d.Object().GetByPath(tx, newPkg.PackageID, newChildPathJoined)
	if err == nil {
		return fuse.ErrExists
	}

	err = d.Object().HasObjectWithPrefix(tx, newPkg.PackageID, newChildPathJoined+clitypes.ObjectPathSeparator)
	if err == nil {
		return fuse.ErrExists
	}
	if err != gorm.ErrRecordNotFound {
		return err
	}

	// 按理来说还需要检查远程文件所在的文件夹是否存在，但对象存储是不存文件夹的，所以不检查，导致的后果就是移动时会创建不存在的文件夹

	oldPkg, err := d.Package().GetByFullName(tx, oldChildPath[0], oldChildPath[1])
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return fuse.ErrNotExists
		}
		return err
	}

	// 都不存在，就开始移动文件
	oldObj, err := d.Object().GetByPath(tx, oldPkg.PackageID, oldChildPathJoined)
	if err == nil {
		oldObj.PackageID = newPkg.PackageID
		oldObj.Path = newChildPathJoined

		return d.Object().BatchUpdate(tx, []clitypes.Object{oldObj})
	}
	if err != gorm.ErrRecordNotFound {
		return err
	}

	err = d.Object().HasObjectWithPrefix(tx, oldPkg.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator)
	if err == nil {
		return d.Object().MoveByPrefix(tx,
			oldPkg.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator,
			newPkg.PackageID, newChildPathJoined+clitypes.ObjectPathSeparator,
		)
	}
	if err == gorm.ErrRecordNotFound {
		return fuse.ErrNotExists
	}
	return err
}
