package utils

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

	"github.com/AlliotTech/openalist/internal/errs"

	"github.com/AlliotTech/openalist/internal/conf"
	log "github.com/sirupsen/logrus"
)

// CopyFile File copies a single file from src to dst
func CopyFile(src, dst string) error {
	var err error
	var srcfd *os.File
	var dstfd *os.File
	var srcinfo os.FileInfo

	if srcfd, err = os.Open(src); err != nil {
		return err
	}
	defer srcfd.Close()

	if dstfd, err = CreateNestedFile(dst); err != nil {
		return err
	}
	defer dstfd.Close()

	if _, err = CopyWithBuffer(dstfd, srcfd); err != nil {
		return err
	}
	if srcinfo, err = os.Stat(src); err != nil {
		return err
	}
	return os.Chmod(dst, srcinfo.Mode())
}

// CopyDir Dir copies a whole directory recursively
func CopyDir(src, dst string) error {
	var err error
	var fds []os.DirEntry
	var srcinfo os.FileInfo

	if srcinfo, err = os.Stat(src); err != nil {
		return err
	}
	if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
		return err
	}
	if fds, err = os.ReadDir(src); err != nil {
		return err
	}
	for _, fd := range fds {
		srcfp := path.Join(src, fd.Name())
		dstfp := path.Join(dst, fd.Name())

		if fd.IsDir() {
			if err = CopyDir(srcfp, dstfp); err != nil {
				fmt.Println(err)
			}
		} else {
			if err = CopyFile(srcfp, dstfp); err != nil {
				fmt.Println(err)
			}
		}
	}
	return nil
}

// SymlinkOrCopyFile symlinks a file or copy if symlink failed
func SymlinkOrCopyFile(src, dst string) error {
	if err := CreateNestedDirectory(filepath.Dir(dst)); err != nil {
		return err
	}
	if err := os.Symlink(src, dst); err != nil {
		return CopyFile(src, dst)
	}
	return nil
}

// Exists determine whether the file exists
func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// CreateNestedDirectory create nested directory
func CreateNestedDirectory(path string) error {
	err := os.MkdirAll(path, 0700)
	if err != nil {
		log.Errorf("can't create folder, %s", err)
	}
	return err
}

// CreateNestedFile create nested file
func CreateNestedFile(path string) (*os.File, error) {
	basePath := filepath.Dir(path)
	if err := CreateNestedDirectory(basePath); err != nil {
		return nil, err
	}
	return os.Create(path)
}

// CreateTempFile create temp file from io.ReadCloser, and seek to 0
func CreateTempFile(r io.Reader, size int64) (*os.File, error) {
	if f, ok := r.(*os.File); ok {
		return f, nil
	}
	f, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
	if err != nil {
		return nil, err
	}
	readBytes, err := CopyWithBuffer(f, r)
	if err != nil {
		_ = os.Remove(f.Name())
		return nil, errs.NewErr(err, "CreateTempFile failed")
	}
	if size > 0 && readBytes != size {
		_ = os.Remove(f.Name())
		return nil, errs.NewErr(err, "CreateTempFile failed, incoming stream actual size= %d, expect = %d ", readBytes, size)
	}
	_, err = f.Seek(0, io.SeekStart)
	if err != nil {
		_ = os.Remove(f.Name())
		return nil, errs.NewErr(err, "CreateTempFile failed, can't seek to 0 ")
	}
	return f, nil
}

// GetFileType get file type
func GetFileType(filename string) int {
	ext := strings.ToLower(Ext(filename))
	if SliceContains(conf.SlicesMap[conf.AudioTypes], ext) {
		return conf.AUDIO
	}
	if SliceContains(conf.SlicesMap[conf.VideoTypes], ext) {
		return conf.VIDEO
	}
	if SliceContains(conf.SlicesMap[conf.ImageTypes], ext) {
		return conf.IMAGE
	}
	if SliceContains(conf.SlicesMap[conf.TextTypes], ext) {
		return conf.TEXT
	}
	return conf.UNKNOWN
}

func GetObjType(filename string, isDir bool) int {
	if isDir {
		return conf.FOLDER
	}
	return GetFileType(filename)
}

var extraMimeTypes = map[string]string{
	".apk": "application/vnd.android.package-archive",
}

func GetMimeType(name string) string {
	ext := path.Ext(name)
	if m, ok := extraMimeTypes[ext]; ok {
		return m
	}
	m := mime.TypeByExtension(ext)
	if m != "" {
		return m
	}
	return "application/octet-stream"
}

const (
	KB = 1 << (10 * (iota + 1))
	MB
	GB
	TB
)
