package local

import (
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strconv"

	"github.com/samber/lo"
	"gitlink.org.cn/cloudream/common/pkgs/logger"
	cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
	stgmod "gitlink.org.cn/cloudream/storage/common/models"
	"gitlink.org.cn/cloudream/storage/common/pkgs/storage/types"
	"gitlink.org.cn/cloudream/storage/common/pkgs/storage/utils"
)

type SharedStore struct {
	stg cdssdk.Storage
	cfg cdssdk.LocalSharedStorage
	// lock sync.Mutex
}

func NewSharedStore(stg cdssdk.Storage, cfg cdssdk.LocalSharedStorage) (*SharedStore, error) {
	_, ok := stg.Address.(*cdssdk.LocalStorageAddress)
	if !ok {
		return nil, fmt.Errorf("storage address(%T) is not local", stg)
	}

	return &SharedStore{
		stg: stg,
		cfg: cfg,
	}, nil
}

func (s *SharedStore) Start(ch *types.StorageEventChan) {
	s.getLogger().Infof("component start, LoadBase: %v", s.cfg.LoadBase)
}

func (s *SharedStore) Stop() {
	s.getLogger().Infof("component stop")
}

func (s *SharedStore) WritePackageObject(userID cdssdk.UserID, pkgID cdssdk.PackageID, path string, stream io.Reader) (string, error) {
	relaPath := filepath.Join(utils.MakeLoadedPackagePath(userID, pkgID), path)
	fullPath := filepath.Join(s.cfg.LoadBase, relaPath)
	err := os.MkdirAll(filepath.Dir(fullPath), 0755)
	if err != nil {
		return "", err
	}

	f, err := os.Create(fullPath)
	if err != nil {
		return "", err
	}
	defer f.Close()

	_, err = io.Copy(f, stream)
	if err != nil {
		return "", err
	}

	return filepath.ToSlash(relaPath), nil
}

func (s *SharedStore) ListLoadedPackages() ([]stgmod.LoadedPackageID, error) {
	entries, err := os.ReadDir(s.cfg.LoadBase)
	if os.IsNotExist(err) {
		return nil, nil
	}
	if err != nil {
		s.getLogger().Warnf("list package directory: %v", err)
		return nil, err
	}

	var loadeds []stgmod.LoadedPackageID
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}

		uid, err := strconv.ParseInt(e.Name(), 10, 64)
		if err != nil {
			continue
		}

		userID := cdssdk.UserID(uid)
		pkgs, err := s.listUserPackages(userID, fmt.Sprintf("%v", userID))
		if err != nil {
			continue
		}

		loadeds = append(loadeds, pkgs...)
	}

	return loadeds, nil
}

func (s *SharedStore) listUserPackages(userID cdssdk.UserID, userIDStr string) ([]stgmod.LoadedPackageID, error) {
	userDir := filepath.Join(s.cfg.LoadBase, userIDStr)
	entries, err := os.ReadDir(userDir)
	if os.IsNotExist(err) {
		return nil, nil
	}
	if err != nil {
		s.getLogger().Warnf("list package directory: %v", err)
		return nil, err
	}

	var pkgs []stgmod.LoadedPackageID
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}

		pkgID, err := strconv.ParseInt(e.Name(), 10, 64)
		if err != nil {
			continue
		}

		pkgs = append(pkgs, stgmod.LoadedPackageID{
			UserID:    userID,
			PackageID: cdssdk.PackageID(pkgID),
		})
	}

	return pkgs, nil
}

func (s *SharedStore) PackageGC(avaiables []stgmod.LoadedPackageID) error {
	log := s.getLogger()

	entries, err := os.ReadDir(s.cfg.LoadBase)
	if err != nil {
		log.Warnf("list storage directory: %s", err.Error())
		return err
	}

	// userID->pkgID->pkg
	userPkgs := make(map[string]map[string]bool)
	for _, pkg := range avaiables {
		userIDStr := fmt.Sprintf("%v", pkg.UserID)

		pkgs, ok := userPkgs[userIDStr]
		if !ok {
			pkgs = make(map[string]bool)
			userPkgs[userIDStr] = pkgs
		}

		pkgIDStr := fmt.Sprintf("%v", pkg.PackageID)
		pkgs[pkgIDStr] = true
	}

	userDirs := lo.Filter(entries, func(info fs.DirEntry, index int) bool { return info.IsDir() })
	for _, dir := range userDirs {
		pkgMap, ok := userPkgs[dir.Name()]
		// 第一级目录名是UserID，先删除UserID在StoragePackage表里没出现过的文件夹
		if !ok {
			rmPath := filepath.Join(s.cfg.LoadBase, dir.Name())
			err := os.RemoveAll(rmPath)
			if err != nil {
				log.Warnf("removing user dir %s: %s", rmPath, err.Error())
			} else {
				log.Debugf("user dir %s removed by gc", rmPath)
			}
			continue
		}

		pkgDir := filepath.Join(s.cfg.LoadBase, dir.Name())
		// 遍历每个UserID目录的packages目录里的内容
		pkgs, err := os.ReadDir(pkgDir)
		if err != nil {
			log.Warnf("reading package dir %s: %s", pkgDir, err.Error())
			continue
		}

		for _, pkg := range pkgs {
			if !pkgMap[pkg.Name()] {
				rmPath := filepath.Join(pkgDir, pkg.Name())
				err := os.RemoveAll(rmPath)
				if err != nil {
					log.Warnf("removing package dir %s: %s", rmPath, err.Error())
				} else {
					log.Debugf("package dir %s removed by gc", rmPath)
				}
			}
		}
	}

	return nil
}

func (s *SharedStore) getLogger() logger.Logger {
	return logger.WithField("SharedStore", "Local").WithField("Storage", s.stg.String())
}

type PackageWriter struct {
	pkgRoot     string
	fullDirPath string
}

func (w *PackageWriter) Root() string {
	return w.pkgRoot
}

func (w *PackageWriter) Write(path string, stream io.Reader) (string, error) {
	fullFilePath := filepath.Join(w.fullDirPath, path)
	err := os.MkdirAll(filepath.Dir(fullFilePath), 0755)
	if err != nil {
		return "", err
	}

	f, err := os.Create(fullFilePath)
	if err != nil {
		return "", err
	}
	defer f.Close()

	_, err = io.Copy(f, stream)
	if err != nil {
		return "", err
	}

	return filepath.ToSlash(filepath.Join(w.pkgRoot, path)), nil
}
