package utils

import (
	"archive/tar"
	"fmt"
	"hash/fnv"
	"io"
	"os"
	"strings"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

type ImportCallback = func(error)

type MockReadCloser struct {
	Reader io.Reader
}

func (mc *MockReadCloser) Read(b []byte) (int, error) {
	return mc.Reader.Read(b)
}

func (mc *MockReadCloser) Close() error {
	return nil
}

func ReadTar(packagePath string, task func(fileName string, fh *tar.Header, r io.Reader)) error {
	f, err := os.Open(packagePath)
	if err != nil {
		return err
	}
	defer func() {
		_ = f.Close()
	}()

	return ReadTarWithReader(tar.NewReader(f), task)
}

func ReadTarWithReader(reader *tar.Reader, task func(fileName string, fh *tar.Header, r io.Reader)) error {
	for {
		fh, err := reader.Next()
		if err == io.EOF {
			break
		}

		if err != nil {
			log.Error("reader tar failed", zap.Error(err))
			return nil
		}

		fileStart := strings.LastIndex(fh.Name, "/")
		fileName := fh.Name
		if fileStart >= 0 {
			fileName = fh.Name[fileStart+1:]
		}

		task(fileName, fh, reader)
	}

	return nil
}

func ExtractDecompressFile(reader *tar.Reader, parentPath string) (int64, error) {
	if err := CreateDirIfNotExist(parentPath); err != nil {
		return 0, err
	}

	if !strings.HasSuffix(parentPath, "/") {
		parentPath += "/"
	}

	var totalSize int64
	for {
		header, err := reader.Next()
		if err == io.EOF {
			break
		}

		if err != nil {
			log.Error("reader tar failed", zap.Error(err))
			return 0, nil
		}

		switch header.Typeflag {
		case tar.TypeDir:
			if err := os.MkdirAll(fmt.Sprintf("%s%s", parentPath, header.Name), 0o755); err != nil {
				return 0, errors.Annotate(err, "mkdir failed")
			}
		case tar.TypeReg:
			fileStart := strings.LastIndex(header.Name, "/")
			if fileStart >= 0 {
				if err := CreateDirIfNotExist(fmt.Sprintf("%s%s", parentPath, header.Name[0:fileStart])); err != nil {
					return 0, err
				}
			}
			outFile, err := os.Create(fmt.Sprintf("%s%s", parentPath, header.Name))
			if err != nil {
				return 0, errors.Annotate(err, "create file failed")
			}
			if _, err := io.Copy(outFile, reader); err != nil {
				log.Error("copy file failed", zap.String("file", fmt.Sprintf("%s%s", parentPath, header.Name)), zap.Error(err))
				// return errors.Annotate(err, "copy file failed")
			}

			totalSize += header.Size

			_ = outFile.Close()
		default:
			return 0, errors.Annotate(err, fmt.Sprintf("unknown type %d in %s", header.Typeflag, header.Name))
		}
	}

	return totalSize, nil
}

func CreateDirIfNotExist(dir string) error {
	_, err := os.Stat(dir)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}

		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
			return err
		}
	}

	return nil
	//if {
	//	if err := os.MkdirAll(dir], os.ModePerm); err != nil {
	//		return err
	//	}
	//}
}

func FnvHash(raw string) string {
	hash := fnv.New64()
	if _, err := hash.Write([]byte(raw)); err != nil {
		// impossible path
		log.Error("failed to write fnv hash", zap.Error(err))
	}
	return fmt.Sprintf("%x", hash.Sum64())
}

func TmpFile(reader io.Reader) (string, error) {
	f, err := os.CreateTemp("", "")
	if err != nil {
		return "", err
	}
	defer func() {
		_ = f.Close()
	}()

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

	return f.Name(), nil
}
