package vfs

import (
	"context"
	"fmt"
	"os"
	"time"

	"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 FuseBucket struct {
	vfs     *Vfs
	bktName string
	modTime time.Time
	perm    os.FileMode
}

func newBucketFromCache(c cache.CacheEntryInfo, vfs *Vfs) fuse.FsDir {
	return &FuseBucket{
		vfs:     vfs,
		bktName: c.PathComps[len(c.PathComps)-1],
		modTime: c.ModTime,
		perm:    c.Perm,
	}
}

func (r *FuseBucket) PathComps() []string {
	return []string{r.bktName}
}

func (r *FuseBucket) Name() string {
	return r.bktName
}

func (r *FuseBucket) Size() int64 {
	return 0
}

func (r *FuseBucket) Perm() os.FileMode {
	return r.perm
}

func (r *FuseBucket) ModTime() time.Time {
	return r.modTime
}

func (r *FuseBucket) IsDir() bool {
	return true
}

func (r *FuseBucket) SetModTime(time time.Time) error {
	dir := r.loadCacheDir()
	if dir == nil {
		return fuse.ErrNotExists
	}

	return dir.SetModTime(time)
}

// 如果不存在，应该返回ErrNotExists
func (r *FuseBucket) Child(ctx context.Context, name string) (fuse.FsEntry, error) {
	childPathComps := []string{r.bktName, name}
	ca := r.vfs.cache.Stat(childPathComps)

	if ca == nil {
		// TODO UserID

		pkg, err := r.vfs.db.Package().GetByFullName(r.vfs.db.DefCtx(), r.bktName, name)
		if err == nil {
			dir := r.vfs.cache.LoadDir(childPathComps, &cache.CreateDirOption{
				ModTime: pkg.CreateTime,
			})
			if dir == nil {
				return nil, fuse.ErrNotExists
			}

			return newPackageFromCache(dir.Info(), r.vfs), nil
		}

		if err == gorm.ErrRecordNotFound {
			return nil, fuse.ErrNotExists
		}

		return nil, err
	}

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

	return newFileFromCache(*ca, r.vfs), nil
}

func (r *FuseBucket) Children(ctx context.Context) ([]fuse.FsEntry, error) {
	return r.listChildren()
}

func (r *FuseBucket) ReadChildren() (fuse.DirReader, error) {
	ens, err := r.listChildren()
	if err != nil {
		return nil, err
	}

	return newFuseDirReader(ens), nil
}

func (r *FuseBucket) listChildren() ([]fuse.FsEntry, error) {
	var ens []fuse.FsEntry

	infos := r.vfs.cache.StatMany([]string{r.bktName})

	pkgs, err := r.vfs.db.Package().GetBucketPackagesByName(r.vfs.db.DefCtx(), r.bktName)
	if err != nil {
		return nil, err
	}

	pkgMap := make(map[string]*clitypes.Package)
	for _, pkg := range pkgs {
		p := pkg
		pkgMap[pkg.Name] = &p
	}

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

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

	// 顺便创建一下文件夹
	for _, pkg := range pkgMap {
		dir := r.vfs.cache.LoadDir([]string{r.bktName, pkg.Name}, &cache.CreateDirOption{
			ModTime: pkg.CreateTime,
		})
		if dir == nil {
			continue
		}

		ens = append(ens, newPackageFromCache(dir.Info(), r.vfs))
	}

	return ens, nil
}

func (r *FuseBucket) NewDir(ctx context.Context, name string) (fuse.FsDir, error) {
	cache := r.vfs.cache.CreateDir([]string{r.bktName, name})
	if cache == nil {
		return nil, fuse.ErrPermission
	}

	// TODO 用户ID，失败了可以打个日志
	// TODO 生成系统事件
	// 不关注创建是否成功，仅尝试一下
	r.vfs.db.DoTx(func(tx db.SQLContext) error {
		db := r.vfs.db
		bkt, err := db.Bucket().GetByName(tx, r.bktName)
		if err != nil {
			return fmt.Errorf("get bucket: %v", err)
		}

		_, err = db.Package().Create(tx, bkt.BucketID, name, time.Now())
		if err != nil {
			return fmt.Errorf("create package: %v", err)
		}

		return err
	})

	return newPackageFromCache(cache.Info(), r.vfs), nil
}

func (r *FuseBucket) NewFile(ctx context.Context, name string, flags uint32) (fuse.FileHandle, uint32, error) {
	return newFile(r.vfs, ctx, name, r, flags)
}

func (r *FuseBucket) RemoveChild(ctx context.Context, name string) error {
	// TODO 生成系统事件
	return r.vfs.db.DoTx(func(tx db.SQLContext) error {
		d := r.vfs.db

		pkg, err := d.Package().GetByFullName(tx, r.bktName, name)
		if err == nil {
			err = d.Object().HasObjectWithPrefix(tx, pkg.PackageID, "")
			if err == nil {
				return fuse.ErrNotEmpty
			}
			if err != gorm.ErrRecordNotFound {
				return err
			}

			err = d.Package().DeleteComplete(tx, pkg.PackageID)
			if err != nil {
				return err
			}

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

		return r.vfs.cache.Remove([]string{r.bktName, name})
	})
}

func (r *FuseBucket) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error {
	newParentNode := newParent.(FuseNode)
	newParentPath := newParentNode.PathComps()

	// 仅允许在不同桶之间移动
	if len(newParentPath) != 1 {
		return fuse.ErrNotSupported
	}

	d := r.vfs.db
	return d.DoTx(func(tx db.SQLContext) error {
		_, err := d.Package().GetByFullName(tx, newParentPath[0], newName)
		if err == nil {
			// 目标节点已经存在，不能重命名，直接退出
			return fuse.ErrExists
		} else if err != gorm.ErrRecordNotFound {
			return err
		}

		newBkt, err := d.Bucket().GetByName(tx, newParentPath[0])
		if err == nil {
			oldPkg, err := d.Package().GetByFullName(tx, r.bktName, oldName)
			if err == nil {
				err = d.Package().Move(tx, oldPkg.PackageID, newBkt.BucketID, newName)
				if err != nil {
					return err
				}
			} else if err != gorm.ErrRecordNotFound {
				return err
			}

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

		// 有可能是移动文件，所以如果是源文件夹未找到，也尝试进行移动
		return r.vfs.cache.Move([]string{r.bktName, oldName}, []string{newParentPath[0], newName})
	})
}

func (r *FuseBucket) loadCacheDir() *cache.CacheDir {
	var createOpt *cache.CreateDirOption
	bkt, err := r.vfs.db.Bucket().GetByName(r.vfs.db.DefCtx(), r.bktName)
	if err == nil {
		createOpt = &cache.CreateDirOption{
			ModTime: bkt.CreateTime,
		}
	}

	return r.vfs.cache.LoadDir([]string{r.bktName}, createOpt)
}

var _ fuse.FsDir = (*FuseBucket)(nil)
var _ FuseNode = (*FuseBucket)(nil)
