package archives

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

	"github.com/OpenListTeam/OpenList/v4/internal/archive/tool"
	"github.com/OpenListTeam/OpenList/v4/internal/model"
	"github.com/OpenListTeam/OpenList/v4/internal/stream"
	"github.com/OpenListTeam/OpenList/v4/pkg/utils"
)

type Archives struct {
}

func (Archives) AcceptedExtensions() []string {
	return []string{
		".br", ".bz2", ".gz", ".lz4", ".lz", ".mz", ".sz", ".s2", ".xz", ".zz", ".zst", ".tar",
		".tgz", ".tlz4", ".tlz", ".tbz2", ".txz", ".tzst",
	}
}

func (Archives) AcceptedMultipartExtensions() map[string]tool.MultipartExtension {
	return map[string]tool.MultipartExtension{}
}

func (Archives) GetMeta(ss []*stream.SeekableStream, args model.ArchiveArgs) (model.ArchiveMeta, error) {
	fsys, err := getFs(ss[0], args)
	if err != nil {
		return nil, err
	}
	files, err := fsys.ReadDir(".")
	if err != nil {
		return nil, filterPassword(err)
	}

	tree := make([]model.ObjTree, 0, len(files))
	for _, file := range files {
		info, err := file.Info()
		if err != nil {
			continue
		}
		tree = append(tree, &model.ObjectTree{Object: *toModelObj(info)})
	}
	return &model.ArchiveMetaInfo{
		Comment:   "",
		Encrypted: false,
		Tree:      tree,
	}, nil
}

func (Archives) List(ss []*stream.SeekableStream, args model.ArchiveInnerArgs) ([]model.Obj, error) {
	fsys, err := getFs(ss[0], args.ArchiveArgs)
	if err != nil {
		return nil, err
	}
	innerPath := strings.TrimPrefix(args.InnerPath, "/")
	if innerPath == "" {
		innerPath = "."
	}
	obj, err := fsys.ReadDir(innerPath)
	if err != nil {
		return nil, filterPassword(err)
	}
	return utils.SliceConvert(obj, func(src os.DirEntry) (model.Obj, error) {
		info, err := src.Info()
		if err != nil {
			return nil, err
		}
		return toModelObj(info), nil
	})
}

func (Archives) Extract(ss []*stream.SeekableStream, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
	fsys, err := getFs(ss[0], args.ArchiveArgs)
	if err != nil {
		return nil, 0, err
	}
	file, err := fsys.Open(strings.TrimPrefix(args.InnerPath, "/"))
	if err != nil {
		return nil, 0, filterPassword(err)
	}
	stat, err := file.Stat()
	if err != nil {
		return nil, 0, filterPassword(err)
	}
	return file, stat.Size(), nil
}

func (Archives) Decompress(ss []*stream.SeekableStream, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error {
	fsys, err := getFs(ss[0], args.ArchiveArgs)
	if err != nil {
		return err
	}
	isDir := false
	path := strings.TrimPrefix(args.InnerPath, "/")
	if path == "" {
		isDir = true
		path = "."
	} else {
		stat, err := fsys.Stat(path)
		if err != nil {
			return filterPassword(err)
		}
		if stat.IsDir() {
			isDir = true
			outputPath = filepath.Join(outputPath, stat.Name())
			err = os.Mkdir(outputPath, 0700)
			if err != nil {
				return filterPassword(err)
			}
		}
	}
	if isDir {
		err = fs.WalkDir(fsys, path, func(p string, d fs.DirEntry, err error) error {
			if err != nil {
				return err
			}
			relPath := strings.TrimPrefix(p, path+"/")
			dstPath := filepath.Join(outputPath, relPath)
			if !strings.HasPrefix(dstPath, outputPath+string(os.PathSeparator)) {
				return fmt.Errorf("illegal file path: %s", relPath)
			}
			if d.IsDir() {
				err = os.MkdirAll(dstPath, 0700)
			} else {
				dir := filepath.Dir(dstPath)
				err = decompress(fsys, p, dir, func(_ float64) {})
			}
			return err
		})
	} else {
		err = decompress(fsys, path, outputPath, up)
	}
	return filterPassword(err)
}

var _ tool.Tool = (*Archives)(nil)

func init() {
	tool.RegisterTool(Archives{})
}
