package alias

import (
	"context"
	"errors"
	stdpath "path"
	"strings"

	"github.com/AlliotTech/openalist/internal/driver"
	"github.com/AlliotTech/openalist/internal/errs"
	"github.com/AlliotTech/openalist/internal/fs"
	"github.com/AlliotTech/openalist/internal/model"
	"github.com/AlliotTech/openalist/pkg/utils"
)

type Alias struct {
	model.Storage
	Addition
	pathMap     map[string][]string
	autoFlatten bool
	oneKey      string
}

func (d *Alias) Config() driver.Config {
	return config
}

func (d *Alias) GetAddition() driver.Additional {
	return &d.Addition
}

func (d *Alias) Init(ctx context.Context) error {
	if d.Paths == "" {
		return errors.New("paths is required")
	}
	d.pathMap = make(map[string][]string)
	for _, path := range strings.Split(d.Paths, "\n") {
		path = strings.TrimSpace(path)
		if path == "" {
			continue
		}
		k, v := getPair(path)
		d.pathMap[k] = append(d.pathMap[k], v)
	}
	if len(d.pathMap) == 1 {
		for k := range d.pathMap {
			d.oneKey = k
		}
		d.autoFlatten = true
	} else {
		d.oneKey = ""
		d.autoFlatten = false
	}
	return nil
}

func (d *Alias) Drop(ctx context.Context) error {
	d.pathMap = nil
	return nil
}

func (d *Alias) Get(ctx context.Context, path string) (model.Obj, error) {
	if utils.PathEqual(path, "/") {
		return &model.Object{
			Name:     "Root",
			IsFolder: true,
			Path:     "/",
		}, nil
	}
	root, sub := d.getRootAndPath(path)
	dsts, ok := d.pathMap[root]
	if !ok {
		return nil, errs.ObjectNotFound
	}
	for _, dst := range dsts {
		obj, err := d.get(ctx, path, dst, sub)
		if err == nil {
			return obj, nil
		}
	}
	return nil, errs.ObjectNotFound
}

func (d *Alias) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
	path := dir.GetPath()
	if utils.PathEqual(path, "/") && !d.autoFlatten {
		return d.listRoot(), nil
	}
	root, sub := d.getRootAndPath(path)
	dsts, ok := d.pathMap[root]
	if !ok {
		return nil, errs.ObjectNotFound
	}
	var objs []model.Obj
	fsArgs := &fs.ListArgs{NoLog: true, Refresh: args.Refresh}
	for _, dst := range dsts {
		tmp, err := d.list(ctx, dst, sub, fsArgs)
		if err == nil {
			objs = append(objs, tmp...)
		}
	}
	return objs, nil
}

func (d *Alias) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	root, sub := d.getRootAndPath(file.GetPath())
	dsts, ok := d.pathMap[root]
	if !ok {
		return nil, errs.ObjectNotFound
	}
	for _, dst := range dsts {
		link, err := d.link(ctx, dst, sub, args)
		if err == nil {
			if !args.Redirect && len(link.URL) > 0 {
				// 正常情况下 多并发 仅支持返回URL的驱动
				// alias套娃alias 可以让crypt、mega等驱动(不返回URL的) 支持并发
				if d.DownloadConcurrency > 0 {
					link.Concurrency = d.DownloadConcurrency
				}
				if d.DownloadPartSize > 0 {
					link.PartSize = d.DownloadPartSize * utils.KB
				}
			}
			return link, nil
		}
	}
	return nil, errs.ObjectNotFound
}

func (d *Alias) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	reqPath, err := d.getReqPath(ctx, parentDir, true)
	if err == nil {
		return fs.MakeDir(ctx, stdpath.Join(*reqPath, dirName))
	}
	if errs.IsNotImplement(err) {
		return errors.New("same-name dirs cannot make sub-dir")
	}
	return err
}

func (d *Alias) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	srcPath, err := d.getReqPath(ctx, srcObj, false)
	if errs.IsNotImplement(err) {
		return errors.New("same-name files cannot be moved")
	}
	if err != nil {
		return err
	}
	dstPath, err := d.getReqPath(ctx, dstDir, true)
	if errs.IsNotImplement(err) {
		return errors.New("same-name dirs cannot be moved to")
	}
	if err != nil {
		return err
	}
	return fs.Move(ctx, *srcPath, *dstPath)
}

func (d *Alias) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	reqPath, err := d.getReqPath(ctx, srcObj, false)
	if err == nil {
		return fs.Rename(ctx, *reqPath, newName)
	}
	if errs.IsNotImplement(err) {
		return errors.New("same-name files cannot be Rename")
	}
	return err
}

func (d *Alias) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	srcPath, err := d.getReqPath(ctx, srcObj, false)
	if errs.IsNotImplement(err) {
		return errors.New("same-name files cannot be copied")
	}
	if err != nil {
		return err
	}
	dstPath, err := d.getReqPath(ctx, dstDir, true)
	if errs.IsNotImplement(err) {
		return errors.New("same-name dirs cannot be copied to")
	}
	if err != nil {
		return err
	}
	_, err = fs.Copy(ctx, *srcPath, *dstPath)
	return err
}

func (d *Alias) Remove(ctx context.Context, obj model.Obj) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	reqPath, err := d.getReqPath(ctx, obj, false)
	if err == nil {
		return fs.Remove(ctx, *reqPath)
	}
	if errs.IsNotImplement(err) {
		return errors.New("same-name files cannot be Delete")
	}
	return err
}

func (d *Alias) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up driver.UpdateProgress) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	reqPath, err := d.getReqPath(ctx, dstDir, true)
	if err == nil {
		return fs.PutDirectly(ctx, *reqPath, s)
	}
	if errs.IsNotImplement(err) {
		return errors.New("same-name dirs cannot be Put")
	}
	return err
}

func (d *Alias) PutURL(ctx context.Context, dstDir model.Obj, name, url string) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	reqPath, err := d.getReqPath(ctx, dstDir, true)
	if err == nil {
		return fs.PutURL(ctx, *reqPath, name, url)
	}
	if errs.IsNotImplement(err) {
		return errors.New("same-name files cannot offline download")
	}
	return err
}

func (d *Alias) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
	root, sub := d.getRootAndPath(obj.GetPath())
	dsts, ok := d.pathMap[root]
	if !ok {
		return nil, errs.ObjectNotFound
	}
	for _, dst := range dsts {
		meta, err := d.getArchiveMeta(ctx, dst, sub, args)
		if err == nil {
			return meta, nil
		}
	}
	return nil, errs.NotImplement
}

func (d *Alias) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
	root, sub := d.getRootAndPath(obj.GetPath())
	dsts, ok := d.pathMap[root]
	if !ok {
		return nil, errs.ObjectNotFound
	}
	for _, dst := range dsts {
		l, err := d.listArchive(ctx, dst, sub, args)
		if err == nil {
			return l, nil
		}
	}
	return nil, errs.NotImplement
}

func (d *Alias) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
	// alias的两个驱动，一个支持驱动提取，一个不支持，如何兼容？
	// 如果访问的是不支持驱动提取的驱动内的压缩文件，GetArchiveMeta就会返回errs.NotImplement，提取URL前缀就会是/ae，Extract就不会被调用
	// 如果访问的是支持驱动提取的驱动内的压缩文件，GetArchiveMeta就会返回有效值，提取URL前缀就会是/ad，Extract就会被调用
	root, sub := d.getRootAndPath(obj.GetPath())
	dsts, ok := d.pathMap[root]
	if !ok {
		return nil, errs.ObjectNotFound
	}
	for _, dst := range dsts {
		link, err := d.extract(ctx, dst, sub, args)
		if err == nil {
			if !args.Redirect && len(link.URL) > 0 {
				if d.DownloadConcurrency > 0 {
					link.Concurrency = d.DownloadConcurrency
				}
				if d.DownloadPartSize > 0 {
					link.PartSize = d.DownloadPartSize * utils.KB
				}
			}
			return link, nil
		}
	}
	return nil, errs.NotImplement
}

func (d *Alias) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) error {
	if !d.Writable {
		return errs.PermissionDenied
	}
	srcPath, err := d.getReqPath(ctx, srcObj, false)
	if errs.IsNotImplement(err) {
		return errors.New("same-name files cannot be decompressed")
	}
	if err != nil {
		return err
	}
	dstPath, err := d.getReqPath(ctx, dstDir, true)
	if errs.IsNotImplement(err) {
		return errors.New("same-name dirs cannot be decompressed to")
	}
	if err != nil {
		return err
	}
	_, err = fs.ArchiveDecompress(ctx, *srcPath, *dstPath, args)
	return err
}

var _ driver.Driver = (*Alias)(nil)
