package dbfs

import (
	"encoding/gob"
	"path"
	"sync"
	"time"

	"github.com/cloudreve/Cloudreve/v4/ent"
	"github.com/cloudreve/Cloudreve/v4/inventory"
	"github.com/cloudreve/Cloudreve/v4/inventory/types"
	"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
	"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
	"github.com/cloudreve/Cloudreve/v4/pkg/util"
	"github.com/samber/lo"
)

func init() {
	gob.Register(File{})
	gob.Register(shareNavigatorState{})
	gob.Register(map[string]*File{})
	gob.Register(map[int]*File{})
}

var (
	filePool = &sync.Pool{
		New: func() any {
			return &File{
				Children: make(map[string]*File),
			}
		},
	}
	defaultView = &types.ExplorerView{
		PageSize:  defaultPageSize,
		View:      "grid",
		Thumbnail: true,
	}
)

type (
	File struct {
		Model             *ent.File
		Children          map[string]*File
		Parent            *File
		Path              [2]*fs.URI
		OwnerModel        *ent.User
		IsUserRoot        bool
		CapabilitiesBs    *boolset.BooleanSet
		FileExtendedInfo  *fs.FileExtendedInfo
		FileFolderSummary *fs.FolderSummary

		disableView bool
		mu          *sync.Mutex
	}
)

const (
	MetadataSysPrefix           = "sys:"
	MetadataUploadSessionPrefix = MetadataSysPrefix + "upload_session"
	MetadataUploadSessionID     = MetadataUploadSessionPrefix + "_id"
	MetadataSharedRedirect      = MetadataSysPrefix + "shared_redirect"
	MetadataRestoreUri          = MetadataSysPrefix + "restore_uri"
	MetadataExpectedCollectTime = MetadataSysPrefix + "expected_collect_time"
	MetadataSharedOwner         = MetadataSysPrefix + "shared_owner"

	ThumbMetadataPrefix = "thumb:"
	ThumbDisabledKey    = ThumbMetadataPrefix + "disabled"

	pathIndexRoot = 0
	pathIndexUser = 1
)

func (f *File) Name() string {
	return f.Model.Name
}

func (f *File) IsNil() bool {
	return f == nil
}

func (f *File) DisplayName() string {
	if uri, ok := f.Metadata()[MetadataRestoreUri]; ok {
		restoreUri, err := fs.NewUriFromString(uri)
		if err != nil {
			return f.Name()
		}

		return path.Base(restoreUri.Path())
	}

	return f.Name()
}

func (f *File) CanHaveChildren() bool {
	return f.Type() == types.FileTypeFolder && !f.IsSymbolic()
}

func (f *File) Ext() string {
	return util.Ext(f.Name())
}

func (f *File) ID() int {
	return f.Model.ID
}

func (f *File) IsSymbolic() bool {
	return f.Model.IsSymbolic
}

func (f *File) Type() types.FileType {
	return types.FileType(f.Model.Type)
}

func (f *File) Size() int64 {
	return f.Model.Size
}

func (f *File) SizeUsed() int64 {
	return lo.SumBy(f.Entities(), func(item fs.Entity) int64 {
		return item.Size()
	})
}

func (f *File) UpdatedAt() time.Time {
	return f.Model.UpdatedAt
}

func (f *File) CreatedAt() time.Time {
	return f.Model.CreatedAt
}

func (f *File) ExtendedInfo() *fs.FileExtendedInfo {
	return f.FileExtendedInfo
}

func (f *File) Owner() *ent.User {
	parent := f
	for parent != nil {
		if parent.OwnerModel != nil {
			return parent.OwnerModel
		}
		parent = parent.Parent
	}

	return nil
}

func (f *File) OwnerID() int {
	return f.Model.OwnerID
}

func (f *File) Shared() bool {
	return len(f.Model.Edges.Shares) > 0
}

func (f *File) Metadata() map[string]string {
	if f.Model.Edges.Metadata == nil {
		return nil
	}
	return lo.Associate(f.Model.Edges.Metadata, func(item *ent.Metadata) (string, string) {
		return item.Name, item.Value
	})
}

// Uri returns the URI of the file.
// If isRoot is true, the URI will be returned from owner's view.
// Otherwise, the URI will be returned from user's view.
func (f *File) Uri(isRoot bool) *fs.URI {
	index := 1
	if isRoot {
		index = 0
	}
	if f.Path[index] != nil || f.Parent == nil {
		return f.Path[index]
	}

	// Find the root file
	elements := make([]string, 0)
	parent := f
	for parent.Parent != nil && parent.Path[index] == nil {
		elements = append([]string{parent.Name()}, elements...)
		parent = parent.Parent
	}

	if parent.Path[index] == nil {
		return nil
	}

	return parent.Path[index].Join(elements...)
}

// View returns the view setting of the file, can be inherited from parent.
func (f *File) View() *types.ExplorerView {
	// If owner has disabled view sync, return nil
	owner := f.Owner()
	if owner != nil && owner.Settings != nil && owner.Settings.DisableViewSync {
		return nil
	}

	// If navigator has disabled view sync, return nil
	userRoot := f.UserRoot()
	if userRoot == nil || userRoot.disableView {
		return nil
	}

	current := f
	for current != nil {
		if current.Model.Props != nil && current.Model.Props.View != nil {
			return current.Model.Props.View
		}
		current = current.Parent
	}

	return defaultView
}

// UserRoot return the root file from user's view.
func (f *File) UserRoot() *File {
	root := f
	for root != nil && !root.IsUserRoot {
		root = root.Parent
	}

	return root
}

// Root return the root file from owner's view.
func (f *File) Root() *File {
	root := f
	for root.Parent != nil {
		root = root.Parent
	}

	return root
}

// RootUri return the URI of the user root file under owner's view.
func (f *File) RootUri() *fs.URI {
	return f.UserRoot().Uri(true)
}

func (f *File) Replace(model *ent.File) *File {
	f.mu.Lock()
	delete(f.Parent.Children, f.Model.Name)
	f.mu.Unlock()

	defer f.Recycle()
	replaced := newFile(f.Parent, model)
	if f.IsRootFile() {
		// If target is a root file, the user path should remain the same.
		replaced.Path[pathIndexUser] = f.Path[pathIndexUser]
	}

	return replaced
}

// Ancestors return all ancestors of the file, until the owner root is reached.
func (f *File) Ancestors() []*File {
	return f.AncestorsChain()[1:]
}

// AncestorsChain return all ancestors of the file (including itself), until the owner root is reached.
func (f *File) AncestorsChain() []*File {
	ancestors := make([]*File, 0)
	parent := f
	for parent != nil {
		ancestors = append(ancestors, parent)
		parent = parent.Parent
	}

	return ancestors
}

func (f *File) PolicyID() int {
	root := f
	return root.Model.StoragePolicyFiles
}

// IsRootFolder return true if the file is the root folder under user's view.
func (f *File) IsRootFolder() bool {
	return f.Type() == types.FileTypeFolder && f.IsRootFile()
}

// IsRootFile return true if the file is the root file under user's view.
func (f *File) IsRootFile() bool {
	uri := f.Uri(false)
	p := uri.Path()
	return f.Model.Name == inventory.RootFolderName || p == fs.Separator || p == ""
}

func (f *File) Entities() []fs.Entity {
	return lo.Map(f.Model.Edges.Entities, func(item *ent.Entity, index int) fs.Entity {
		return fs.NewEntity(item)
	})
}

func (f *File) PrimaryEntity() fs.Entity {
	primary, _ := lo.Find(f.Model.Edges.Entities, func(item *ent.Entity) bool {
		return item.Type == int(types.EntityTypeVersion) && item.ID == f.Model.PrimaryEntity
	})
	if primary != nil {
		return fs.NewEntity(primary)
	}

	return fs.NewEmptyEntity(f.Owner())
}

func (f *File) PrimaryEntityID() int {
	return f.Model.PrimaryEntity
}

func (f *File) FolderSummary() *fs.FolderSummary {
	return f.FileFolderSummary
}

func (f *File) Capabilities() *boolset.BooleanSet {
	return f.CapabilitiesBs
}

func newFile(parent *File, model *ent.File) *File {
	f := filePool.Get().(*File)
	f.Model = model

	if parent != nil {
		f.Parent = parent
		parent.mu.Lock()
		parent.Children[model.Name] = f
		if parent.Path[pathIndexUser] != nil {
			f.Path[pathIndexUser] = parent.Path[pathIndexUser].Join(model.Name)
		}

		if parent.Path[pathIndexRoot] != nil {
			f.Path[pathIndexRoot] = parent.Path[pathIndexRoot].Join(model.Name)
		}

		f.CapabilitiesBs = parent.CapabilitiesBs
		f.mu = parent.mu
		parent.mu.Unlock()
	} else {
		f.mu = &sync.Mutex{}
	}

	return f
}

func newParentFile(parent *ent.File, child *File) *File {
	newParent := newFile(nil, parent)
	newParent.Children[child.Name()] = child
	child.Parent = newParent
	newParent.mu = child.mu
	return newParent
}

func (f *File) Recycle() {
	for _, child := range f.Children {
		child.Recycle()
	}

	f.Model = nil
	f.Children = make(map[string]*File)
	f.Path[0] = nil
	f.Path[1] = nil
	f.Parent = nil
	f.OwnerModel = nil
	f.IsUserRoot = false
	f.mu = nil

	filePool.Put(f)
}
